如何更新你所有的 Python 库
了解我如何使用 PySimpleGUI 来更新我的 Python 库
概观
就在几天前,我试图通过使用 python 库中最近引入的功能来解决我的一个问题。没多久就意识到不在了。我使用的是一个如此陈旧的库版本,以至于很多新功能都不存在。
我通过 pip 管理我的包,所以一次升级一个库非常简单。不幸的是,如果你想全部升级,情况并非如此。没有内置的功能让你一次升级所有的软件包。
我最终拼凑了一些东西,这些东西将循环遍历我的包并升级它们。然而,这个解决方案不允许我选择单独的库来升级;这意味着,无论出于什么原因,如果我不想升级我的某个库,我就会被卡住。这就是为什么我决定建立一个 UI,让我挑选要升级的库。
在这个博客里,我把这个作品分享给你,这样你也可以使用它。随意构建和改进它;或者大家有什么建议,留下评论,也许我会更新。
马库斯·斯皮斯克在 Unsplash 上的照片
调味汁
整个脚本不到 100 行,我使用了四个广泛使用的库来完成。
1.子过程
这个库允许我们与命令行交互,并传递我们想要的任何命令。这就是我将如何找出哪些库是过时的,然后升级它们。
2.熊猫
熊猫以处理数据而闻名。在这个实例中,我们将使用它将一个 CSV 文件读入数据帧,这也很适合我们选择的 UI 库:PySimpleGUI
了解有关将文件读入 pandas 的更多信息:
了解如何使用 Python 进行快速数据分析
medium.com](https://medium.com/financeexplained/from-excel-to-databases-with-python-c6f70bdc509b)
3.铼
Re 是 Python Regex 库,它允许我们轻松地匹配模式。在这种情况下,我们将使用它来剔除任何不必要的信息,只显示有用的信息。
了解有关正则表达式的更多信息:
使用 Python 逐步介绍正则表达式
medium.com](https://medium.com/better-programming/introduction-to-regex-8c18abdd4f70)
4.PySimpleGUI
最后,PySimpleGUI 将是我们用于 UI 的库。我们将定义 UI,然后定义事件:
了解有关构建 Python 用户界面的更多信息:
最后,你可以在 10 分钟内找到一个图书馆
towardsdatascience.com](/learn-how-to-quickly-create-uis-in-python-a97ae1394d5) [## 构建用于比较数据的 Python UI
如何快速让您的非技术团队能够比较数据
towardsdatascience.com](/building-a-python-ui-for-comparing-data-13c10693d9e4)
完整的代码
结论
好了,伙计们。在不到 100 行代码中,您可以看到所有 Python 过时的库,并选择想要升级的库。
出于几个原因,您当然应该定期这样做:
- 获取最新的错误修复
- 解决任何漏洞
- 性能增强
- 访问软件包的最新和最强大的功能
希望你觉得有用。
从零开始的随机森林算法
直觉、伪代码和代码
从头开始实现随机森林 ML 算法听起来像是一项令人生畏的任务。仅仅是想到我们要做对多少细节,就很容易感到不知所措。或者至少我是这么觉得的。我从哪里开始?第一步是什么?我如何知道它是否有效?
转折点是当我意识到我需要从而不是思考代码开始。想象一下,我站在算法里面,在一棵树的根部,我会怎么做?然后呢?问这些问题有助于分解和简化问题。回答这些问题将建立对算法内部工作的直觉。在本帖中,我们将和泰坦尼克号数据集一起探讨这些问题和答案。
我们的议程:
- 涵盖随机森林功能的高级概述
- 为一个二进制随机森林分类器编写伪代码
- 解决一些最小的数据预处理需求
- 编写代码(可在此处完整访问
- 对照 scikit-learn 算法检查结果
请注意,这篇帖子的灵感来自于 Fast.ai 机器学习课程,我强烈推荐给那些通过做来学习最好的人。
让我们开始吧!
树
如果你在泰坦尼克号上,你会幸存吗?
“均值”是指那个群体中每个人的平均存活率。
- 开始时,船上每个人的预期存活率是 40%
- 那么对于所有女性,75%
- 而对于所有男性,20%……等等。
对我来说,我是一个女性,说我有一张三等票。按照这个决策树,我在泰坦尼克号上幸存的概率是 50/50。
另一个人,男性,也在第三类,将有 10%的生存机会;除非他未满 15 岁,否则他生还的可能性是 90%。
这是随机森林中的一棵决策树。我们已经被提供了根据哪个特征(即年龄)以及在哪个级别(即 15 岁以下)进行划分。为了预测一个新轮廓的存活几率,我们简单地跟踪这些分支。
现在让我们拿走树枝。想象我们在树根处。
据我们所知,所有样本中,有 40%存活了下来。我们必须决定拆分哪个功能,以及在什么级别上拆分。怎么会?
我们将尝试每一个功能和每一个级别,对每一个组合进行评分,并选择最好的。
for feature in list of features:
for (unique) row in data:
what is the score of we split here?
if the score is better what we have: update it
但是分数是多少呢?我们如何知道哪种分割是最好的?
分数
对于分类问题,一个常用的指标是基尼不纯度(这也是 scikit-learn 的缺省值)。每个分割都有一个基尼系数:
让我们看一个简单的例子:
如果我把女性和男性分开,基尼系数会是多少?
x = [1, 1, 1, 1, 1, 2, 2, 2] #5 female & 4 male
y = [1, 1, 1, 1, 0, 0, 0, 0] #4 survived & 4 died
**Decision Tree:**def check_features:
for feature in list of features:
find_best_split of this featuredef find_best_split(feature):
for (unique) row in data:
left = list of all rows <= current row
right = list of all rows > current row
calculate the score
if the score is better than what we have: update itdef find_gini(left_split, right_split, y):
p(left) = n_left/n_y
p(right) = n_right/n_y
for each outcome: #in this case, Survived or Died
p = probability of this outcome for the left
sum_left += p^2
p = probability of this outcome for the right
sum_right += p^2
gini = weighted average of (1-sum_left) and (1-sum_right)def prediction():
return array of predictions for this tree
随机森林
一个森林由无数的决策树组成。*随机部分呢?*假设我们有一个包含 1000 个样本和 5 个特征的数据集。我们想建 10 棵树。所有的树都会在同一个地方裂开,最后形成 10 棵完全相同的树。这对我们帮助不大。
如果我们每次取数据集的不同部分会怎么样?假设我们想要 100 棵树,这意味着每棵树 10 个样本,这将导致相当高的方差。
有没有办法让我们每次都用 1000 个样品?而且每棵树的数据也略有不同?是: 自举 。我们从数据集中抽取 1000 个样本,进行替换。平均来说,大约 63%的原始观测值将被包括在内。这对于减少过度拟合很重要。
使用 3 个样本自举
使用这种方法,我们将为每棵树提供不同的数据。随机森林的目标是拥有彼此尽可能不同的树,但非常擅长预测给定的数据。然后,如果你有许多不相关的树,每个树都有很高的预测能力,当你把它们加起来时,误差总和将为零。剩下的将是 X 和 y 变量之间的真实关系。
**Random Forest**:def __init__ (x, y, n_trees, sample_size, min_leaf):
for numbers up till 1-n_trees:
create a tree def create_tree():
get sample_size samples, use np.random.choice w/ replacement
create an instance of the Decision Tree class def predict():
average of predictions from n_trees
return binary outcome 0 or 1
我们现在有了这两个类的伪代码。让我们将它转换成代码,并与 scikit-learn 的 RandomForestClassifier 的结果进行比较。
数据
- 输入和目标必须是数字
- 分类特征是标签编码的(如果是有序的)或一个热编码的
*离群值和特征缩放怎么办?*原来随机森林一般对这些免疫。它的决定是有序的。请记住,在每次考虑分割时,我们将样本分为两组:大于分割值的样本,以及小于或等于分割值的样本。如果我们考虑在 15 岁分开:不管你的年龄是 16 岁还是 60 岁,两者都将被放在一组。随机森林很少假设数据的分布,这是一个很好的起点。
以下是预处理后的 Titanic 数据集的输入内容:
现在,让我们关注 2 个特性的子集:Pclass 和 Sex。
代码
第一次拆分
我们回到了第一棵树的根部。让我们写吧
scikit-learn 树告诉我们这是第一次分裂。
我们的树也做了同样的决定!
tree = RandomForest(x_sub, y_train, 1).tree[0]
[output] gini: 0.318; split:1, var: Sex
进一步分裂
如果我们允许 scikit-learn 树再分裂一次(深度=2):
第一次分开后,我们把所有的女人分在一组,所有的男人分在另一组。对于下一次拆分,这两个组将有效地成为他们自己的决策树的根。对于女性来说,下一步的划分是将第三等级从其他等级中分离出来。对男人来说,下一个分裂是把第一阶层从其他阶层中分裂出来。
让我们修改一下我们的伪代码:
def check_features:
for feature in list of features:
find_best_split of this feature
left_hand_side = rows <= our split point
right_hand_side = rows > our split point
DecisionTree(left_hand_side)
DecisionTree(right_hand_side)
结果与 scikit-learn 的结果相匹配:
tree = RandomForest(x_sub, y_train, 1).tree[0]
[output] gini: 0.318; split:1, var: Sextree.lhs
[output] gini: 0.264; split:2, var: Pclasstree.rhs
[output] gini: 0.272; split:1, var: Pclass
预测
让我们加入更多特性,看看我们的模型是如何工作的:
使用 10 个决策树,我们的模型在测试集上有 81%的准确率。分数低于 scikit-learn 的分数。但是对于一个我们从零开始构建的算法来说还不错!
所以你看,从零开始建立一个随机的森林并不一定是令人生畏的。学习过程的这一部分让我能够区分出算法的哪些部分我真正理解了,哪些部分我还需要继续努力。有时候,除非你尝试自己去实现,否则你不会明白。
谢谢你让我分享我所学到的东西。我希望这篇文章对你有所帮助。欢迎提出问题和建议!
参考资料:
- J.霍华德, Fast.ai 机器学习,第 7 课
- G.詹姆斯、d .威滕、w .特雷弗和 r .蒂布拉尼,统计学习简介,第 5 章
直到下一次,
V
使用 Python Plotly 库和 Web 服务构建实时仪表板
变更数据
编者按: 走向数据科学 是一份以数据科学和机器学习研究为主的中型刊物。我们不是健康专家或流行病学家,本文的观点不应被解释为专业建议。想了解更多关于疫情冠状病毒的信息,可以点击 这里 。
仪表板是一个图形界面,提供与特定现象或业务相关的一些关键指标的综合视图。在这篇文章中,我将一步一步地展示我们如何使用 Python Plotly 支线剧情构建一个仪表板来实时执行新冠肺炎病例的每日报告。我们的目标是制作一个实时仪表板,如下所示:
显示全球新冠肺炎数据的仪表板
仪表盘的实时版本可从以下网址获得
https://covid-19-data-visualizer.herokuapp.com/
您在访问实时仪表盘时可能会遇到一些延迟,因为它是使用免费的 Heroku 帐户部署的,处理资源有限。
数据源和 web 服务
因为我们将基于实时数据构建一个仪表板,所以数据源来自 web 服务,而不是静态的 CSV 或文本文件。简而言之,web 服务是一个在线数据库,我们可以通过它联系并请求一些想要的信息,然后将它们获取到我们的应用程序中。
在本教程中,我们将使用从 Esri 提供的 Web 服务中获得的新冠肺炎数据来构建我们的仪表板。来自该网络服务的新冠肺炎数据每天更新。****
先决条件 Python 库
从 Github 下载源代码
为了让你更轻松地学习本教程,你可以从我的 Github 库获得完整的源代码(新冠肺炎 _ 仪表板. ipynb )。
您可以使用我的源代码作为参考来理解构建仪表板的过程,我将在下面的部分中介绍这个过程。
构建新冠肺炎控制板的步骤
**第 1 部分:**设置 Python 库
我们通过导入本教程所需的所有必要库来开始我们的 Python 脚本。
用于导入库的 Python 代码
第 2 部分:从 web 服务请求数据
接下来,我们访问 esri 网站 以获取一个查询 URL,并将该 URL 复制并粘贴到我们脚本中的 Python request 模块,以向 web 服务发送 HTTP 请求(第 1 行)。HTTP 请求将以 JSON 格式从 web 服务向我们的应用程序返回最新的新冠肺炎数据(第 2 行)。我们从" features "属性中选择数据,并使用它来构建一个 Pandas dataframe,df (第 3 行)。
esri Web 服务网站
请求 web 服务的 Python 代码
我们可以使用熊猫头方法预览返回的新冠肺炎数据的前五条记录。
预览数据的 Python 代码
web 服务数据预览
要更详细地查看属性值,我们只需选择其中一项。
查看属性的 Python 代码
单个记录的属性
从预览中,我们可以看到新冠肺炎记录由一个字典列表组成,其中包括地理属性(如省 _ 州、国家 _ 地区、纬度、经度 _ )和数字属性(如确诊、痊愈、死亡)。此外,它还包括一个时间戳属性( Last_Update )。
第 3 部分:转换数据
从上一部分中,我们了解到新冠肺炎记录的结构是一个字典列表,在这里我们试图将它们转换成一个新的 Pandas 数据帧,df_final 。为此,我们首先使用 tolist 方法将字典转换成一个列表,data_list (第 1 行)。接下来使用 data_list 构建一个新的数据帧 df_final (第 2 行)。接下来,我们使用 set_index 方法将“OBJECTID”属性设置为每条记录的索引(第 3 行)。最后,重新排序 df_final 数据框中的列(第 4 行)并预览转换后的记录(第 5 行)。
转换数据的 Python 代码
转换数据帧
第 4 部分:清理数据
显然,转换后的数据看起来不错,但这里仍然存在两个问题:
- 在“最后 _ 更新”和“省 _ 州”列中有一些值 na 丢失。
- “ Last_Update ”列中的值是一个以毫秒为单位的时间戳,它对日期没有太多意义。
要处理第一个问题,我们可以使用 dropna 方法从列" Last_Update “中删除缺失的值(第 5 行),使用 fillna 方法用一个空字符串替换列” Province_State "中所有缺失的值(第 6 行)。
为了解决第二个问题,我们编写了一个函数 convertTime ,使用 fromtimestamp 方法(第 1–3 行)*,将时间戳转换为格式为" yyyy-mm-dd-hh-mm-ss" 的日期。*列“Last_Update”首先除以 1000(第 8 行),然后对整个列应用 convertTime 函数(第 9 行)。
清理数据的 Python 代码
清理数据框
第 5 部分:聚合数据
目前,记录中确诊、康复和死亡病例的累计总数是以省为基础的,而不是以国家为基础的。构建这个仪表板的目的之一是显示排名前 10 位的国家列表。因此,我们需要对“确诊”、“死亡”和“恢复”列进行求和,并将求和值按“国家/地区分组,从而执行数据聚合。聚集的数据被分配给新的数据帧 df_total 。
聚合数据的 Python 代码
汇总数据
第 6 部分:计算每日新冠肺炎病例总数
在此阶段,我们准备使用前面部分的转换和汇总数据来计算两个级别的新冠肺炎病例总数:
- 全球一级
- 十大国家级别
第一步:全局级
全球每日新冠肺炎病例总数的计算非常简单。我们只是将求和的方法应用到 df_final 的“确诊”、痊愈、死亡列。 sum 方法将合计三列中每一列的所有值,并将最终总和分配给它们各自的变量 total_confirmed 、 total_recovered 和 total_deaths 。
用于计算全球范围内每日新冠肺炎病例总数的 Python 代码
第二步:十大国家级别
Pandas 提供了一种简便的方法,即 nlargest ,它使我们能够在数据帧的特定列中选择前十个值。为了选择确诊病例最多的前 10 个国家,我们向 nlargest 方法(第 1 行)传递一个 n 最大值和一个列名,并将返回的数据帧分配给一个新变量 df_top10 。接下来,我们生成国家名称列表(第 2 行)和确诊病例总数列表(第 3 行),并将它们分别分配给两个新变量, top10_countries_1 和 top10_confirmed,。
接下来,重复上面类似的步骤,以获得已康复(第 5-7 行)和死亡病例(第 9-11 行)的前十个国家数据。
Python 代码获取前 10 个国家和确诊、恢复和死亡病例数
第 7 部分:使用 Python Plotly 子情节构建仪表板
仪表板应显示以下信息:
- 世界各地每日确诊、恢复和死亡病例总数
- 全球每个国家的确诊、痊愈和死亡病例总数。
- 确诊、康复和死亡病例数最高的前十个国家。
我们将使用 Python Plotly 支线剧情创建几个支线剧情(每个信息部分一个)并将它们加入到一个单一的仪表板中。
第一步:初始化支线剧情布局
Python Plotly 库提供了一个 make_subplots 函数,使我们能够初始化 subplots 的布局安排。我们可以设置行数和列数来定位仪表板中的每个子情节。
在这种情况下,我们在支线剧情布局中定义了 4 行 6 列。我们出发了
第 1 行第 1 列:
- 散点图——在地图上显示每个国家的确诊、痊愈和死亡病例总数
第 1 行第 4–6 列:
- 指示图——显示全球每日确诊、痊愈和死亡病例总数
第 2 行第 4 列:
- 条形图 —显示确诊病例最多的前 10 个国家
第 3 行第 4 列:
- 条形图 —显示恢复案例数量最多的前 10 个国家
第 4 行第 4 列:
- 条形图 —显示死亡病例最多的前 10 个国家
初始化子情节布局的 Python 代码
步骤 2:创建注释文本
在为散点图生成子图之前(将在下一步中呈现),我们需要定义一个注释文本。每当用户将鼠标悬停在地图上的某个国家上方时,就会显示注释文本。注释文本应显示国家名称、确诊病例、死亡病例和恢复病例以及最后更新日期。
创建注释文本的 Python 代码
地图上的注释文本
第三步:创建子剧情——散点图
为了创建一个散点地图,我们可以使用 Python Plotly go。散点图对象。我们从先前生成的 dataframe、 df_final 到 lon 和 lat 属性(第 4–5 行)中设置“ Long_ 和“ Lat ”数据。接下来,我们将注释文本 df_final[“text”] 设置为 hovertext 属性。每当用户将鼠标悬停在某个国家/地区上方时,这将使预定义的注释文本(来自步骤 2)显示在地图上。
接下来,我们继续设置标记的参数值,如大小、不透明度、符号、颜色等(第 8–23 行)。我希望在这里强调的参数是“颜色”属性。我们将确诊病例的数据序列 df_final[‘Confirmed’] 设置为“color”属性(第 19 行)。出发。散点图对象将根据每个国家的确诊病例水平为地图上显示的标记(方框)生成色标。
最后,我们将散点图的这个子图放置在第 1 行第 1 列(第 27 行)。
创建散点图的 Python 代码
散点图
第四步:创建子剧情—指示器
我们使用 Plotly go 创建了三个指标,显示全球每日确诊、痊愈和死亡病例总数。指示器对象。我们将 *total_confirmed、设置为 value property(第 4 行),将标题设置为“ Confirmed Caes ”(第 5 行)。*这将生成一个标题为“确诊病例的指示器,确诊病例总数将显示在标题下方。最后,我们将指示器的这个子图放置在第 1 行第 4 列(第 7 行)。
我们重复上述类似步骤,为恢复病例和死亡病例创建指标,并将它们放置到适当的行和列编号(第 10–26 行)。
Python 代码生成三个指标
显示确诊、康复和死亡病例的三个指标
第五步:创建支线剧情——条形图
在这一阶段,我们将着手创建三个条形图,以显示确诊、恢复和死亡病例数最高的前十个国家。我们使用 Plotly go.bar 对象创建相关的条形图。
为了创建确诊病例的条形图,我们将前 10 个国家列表和前 10 个数字列表(来自第 6 部分步骤 2)分别设置为 x 轴和 y 轴属性(第 3–4 行)。最后,我们将条形图定位到适当的行和列编号(第 9 行)。
我们重复类似的步骤来制作条形图,根据康复和死亡病例的数量显示前 10 个国家,并将它们放置到适当的行号和列号(第 10-26 行)。
Python 代码创建条形图来显示排名前十的国家
确诊、康复和死亡病例最多的前十个国家
步骤 6:完成布局设置
这是最后一步,我们将完成支线剧情的布局设置:
- 模板(第 2 行)-为仪表板设置深色主题。
- 标题(第 3 行)-为仪表板设置标题,并将上次更新的内容附加到标题中。
- 图例(第 4–6 行)—将图例设置为可见,并将其设置为水平方向。定义坐标 x 和 y 以显示图例。
- geo(第 7–14 行)-将显示的地图设置为正交地球,并在地图上显示海岸线、陆地和海洋。
- 注释(第 16–24 行)-在操控板上设置附加注释文本,以在特定坐标 x 和 y 处显示数据源链接
最后,我们准备呈现我们的仪表板(第 27 行)。
最终确定仪表板布局的 Python 代码
新冠肺炎仪表板
第 8 部分:仪表板导航
带标签的新冠肺炎仪表板
乍看之下,我们可以通过仪表板右上角的三个数字指示器轻松查看每日确诊、康复和死亡病例总数。
在这三个数字指标下面,有三个条形图显示了确诊、恢复和死亡病例数最高的前十个国家。
要查看单个国家/地区的新冠肺炎数据,我们只需将鼠标放在地图上的一个标记(正方形)上,就会出现一个注释文本,显示该国家/地区报告的确诊、康复和死亡病例数。
显示所选国家/地区的新冠肺炎详细信息的注释文本
我们可以使用平移工具旋转正交地图。只需点击仪表盘右上角的平移工具,然后点击地图并向左或向右拖动即可。
点击平移工具
旋转地图
最后的想法
仪表板只是由几个支线剧情组成的可视图形,我们可以一部分一部分地构建它们。一旦我们熟悉了 Python Plotly 库来创建单独的图,我们就可以使用 Plotly Subplots 特性轻松地将它们集成到仪表板中。
让我们的仪表板变得实时的关键是不断地使用来自 web 服务的最新数据,并更新仪表板上的数据。这可以使用 Python 请求模块轻松完成。在我们的例子中,我们可以每天重新启动我们的仪表板,运行在后端的请求模块将从 web 服务请求最新的信息,并在仪表板上显示最新的新冠肺炎数据。
我希望您能从本教程中受益,并应用这里介绍的过程为您关心的数据创建其他仪表板。
参考
使用 Spark 结构化流和微服务构建实时预测管道
在本教程中,我们将讨论在处理低延迟数据管道时解耦机器学习模型的好处
凯文·Ku 在 Unsplash 上的照片
我们将为机器学习预测建立一个实时管道。我们将使用的主要框架是:
- Spark 结构化流:成熟易用的流处理引擎
- Kafka :我们将使用 Kafka 的融合版本作为我们的流媒体平台
- Flask :用于构建 RESTful 微服务的开源 python 包
- Docker :用于本地启动 kafka 集群
- Jupyter 实验室:我们运行代码的环境
- NLTK :带有预训练模型的 python 的 NLP 库。
TL;DR:代码在GitHub上。
将 ML 模型构建为微服务的优势
在实时 ML 管道中,我们以两种方式嵌入模型:将模型直接用于执行处理的框架中,或者将模型单独解耦到微服务中。通过构建 ML 模型的包装器,我们需要额外的努力,为什么要这么麻烦呢?有两大优势。首先,当我们想要部署一个新模型时,我们不需要部署整个管道,我们只需要公开一个新的微服务版本。其次,它给了你更多的能力去测试 ML 模型的不同版本。例如,我们可以使用 canary 部署,在模型的version1
上使用 80%的数据流,在version2
上使用 20%的数据流。一旦我们对version2
的质量感到满意,我们就会向它转移越来越多的流量。
现在让我们深入研究应用程序的开发。
步骤 1:运行 docker compose 来启动 kafka 集群
为了构建集群,我们将使用一个docker-compose
文件来启动所有需要的 docker 容器:zookeeper 和一个代理。
现在简单地说,kafka 是一个分布式流媒体平台,能够处理大量的消息,这些消息被组织或分组到主题中。为了能够并行处理一个主题,必须将它分成多个分区,来自这些分区的数据存储在称为代理的独立机器中。最后,zookeeper 用于管理集群中代理的资源。为了读写 kafka 集群,我们需要一个代理地址和一个主题。
docker-compose
将在端口2181
启动zookeper
,在端口9092
启动kafka broker
。除此之外,我们使用另一个 docker 容器kafka-create-topic
的唯一目的是在 kafka broker 中创建一个主题(称为 test)。
要启动 kafka 集群,我们必须在定义 docker compose 文件的同一文件夹中运行以下命令行指令:
docker-compose up
这将启动所有带有日志的 docker 容器。我们应该在控制台中看到类似这样的内容:
步骤 2:构建和部署微服务
我们将 REST 协议用于我们的 web 服务。我们将使用 NLTK 的Vader
算法进行情感分析。这是一个预先训练好的模型,所以我们只能关注预测部分:
[@app](http://twitter.com/app).route('/predict', methods=['POST'])
def predict():
result = sid.polarity_scores(request.get_json()['data'])
return jsonify(result)
我们正在创建一个接收到形式为{"data": "some text"}
的JSON
消息的POST
请求,其中字段data
包含一个句子。我们将应用该算法,并将响应作为另一个JSON
发送回去。
要运行该应用程序,只需运行:
python app.py
休息服务将在[http://127.0.0.1:9000/predict](http://127.0.0.1:9000/predict)
开始。
步骤 3:使用 Kafka 依赖项启动 pySpark
在我们启动 Jupyter 实验室笔记本之后,我们需要确保我们拥有 kafka jar 作为 spark 的依赖项,以便能够运行代码。在笔记本的第一个单元格中添加以下内容:
import os
os.environ['PYSPARK_SUBMIT_ARGS'] = "--packages=org.apache.spark:spark-sql-kafka-0-10_2.11:2.4.4 pyspark-shell"
接下来,我们可以使用findspark
包启动 pySpark:
import findspark
findspark.init()
第四步:运行卡夫卡制作程序
为了能够实时消费数据,我们首先必须将一些消息写入 kafka。我们将使用 python 中的confluent_kafka
库来编写一个生产者:
我们将发送与之前相同的JSON
消息{"data": value}
,其中 value 是预定义列表中的一个句子。对于我们写入队列的每条消息,我们还需要分配一个键。我们将根据uuid
随机分配一个,以实现集群中的良好分布。最后,我们还运行一个flush
命令来确保所有的消息都被发送。
一旦我们运行了confluent_kafka_producer
,我们应该会收到一个日志,告诉我们数据已经正确发送:
we’ve sent 6 messages to 127.0.0.1:9092
第五步:开始从卡夫卡那里读取数据
如前所述,我们将使用 Spark 结构化流来实时处理数据。这是一个易于使用的 API,将微批量数据视为数据帧。我们首先需要将输入数据读入数据帧:
df_raw = spark \
.readStream \
.format('kafka') \
.option('kafka.bootstrap.servers', bootstrap_servers) \
.option("startingOffsets", "earliest") \
.option('subscribe', topic) \
.load()
startingOffset
是earliest
,表示每次运行代码时,我们将读取队列中的所有数据。
该输入将包含不同的列,表示 kafka 的不同度量,如键、值、偏移量等。我们只对值、实际数据感兴趣,我们可以运行一个转换来反映这一点:
df_json = df_raw.selectExpr('CAST(value AS STRING) as json')
步骤 6:创建一个应用 ML 模型的 UDF
在结构化流中,我们可以使用用户定义的函数,这些函数可以应用于数据帧中的每一行。
def apply_sentiment_analysis(data):
import requests
import json
result = requests.post('[http://localhost:9000/predict'](http://localhost:9000/predict'), json=json.loads(data))
return json.dumps(result.json())
我们需要在函数中进行导入,因为这是一段可以分布在多台机器上的代码。我们向端点发送请求并返回响应。
vader_udf = udf(lambda data: apply_sentiment_analysis(data), StringType())
我们将调用我们的 udf 为vader_udf
,它将返回一个新的字符串列。
步骤 7:应用 vader udf
在这最后一步,我们将看到我们的结果。输入数据的格式在JSON
中,我们可以将其转换成一个字符串。为此,我们将使用助手函数from_json
。我们可以对情绪分析算法的输出列做同样的事情,它也有JSON
格式:
我们可以在控制台中显示我们的结果。因为我们正在使用笔记本,你将只能从你在 Jupyter 启动的终端上看到它。命令trigger(once=True)
将只运行一小段时间的流处理并显示输出。
这就是它的乡亲,我希望你喜欢这个教程,并发现它有用。我们看到了如何通过使用结构化流 API 和调用 ML 模型的微服务,我们可以构建一个强大的模式,它可以成为我们下一个实时应用的主干。
构建推荐引擎在 Spark 中推荐图书
使用协同过滤来预测未读书籍在“好阅读”上的评级
沃伦·巴菲特曾被问及成功的关键,他指着旁边的一摞书说:“每天像这样读 500 页。知识就是这样运作的。它就像复利一样累积起来。你们所有人都可以做到,但我保证你们中没有多少人会去做。
书籍是我们大多数人发展和获得观点的最佳资源。我本人喜欢读书。一旦我喜欢上了一本书,我就有一个习惯,那就是去找好书,或者请有类似品味的人给我推荐下一系列我可能会喜欢的书。
人工智能通过根据过去的数据向我们推荐书籍、电影和产品,节省了我们分析不同选项的时间和精力,从而使我们的世界变得如此简单。事实上,有时机器可以比我们想象的更好地推荐我们,因为它们不会受到情绪偏见的影响。
在这篇博客中,我将解释我是如何使用“好书”数据在 Spark 中构建推荐引擎的。我使用了 Spark 库中的交替最小二乘(ALS)算法,这基本上是网飞竞赛获奖者发表的论文的实现。在深入研究实现之前,让我先解释一下 ALS 算法。
如果你想直接进入“好书”的问题,请跳过这一部分。
交替最小二乘法背后的直觉
对于喜欢阅读学术论文的人来说,下面是论文的链接:https://data jobs . com/data-science-repo/Recommender-Systems-% 5 bnet flix % 5d . pdf。
要了解不同类型的推荐系统,请访问我的其他博客,在那里我解释了不同类型的推荐系统,如基于内容、基于元数据和基于协同过滤的方法。以下是这些系列博客的链接:https://medium . com/analytics-vid hya/the-world-of-recommender-systems-e4ea 504341 AC?source = friends _ link&sk = 508 a 980d 8391 DAA 93530 a 32 e 9 c 927 a 87
基本上,基于内容的推荐系统就是根据以前产品的内容来推荐一些产品。如果你读了一本名为《思考致富》的书,你会被推荐其他有类似内容(或文本)的书,比如《唤醒内心的巨人》,因为它们都是自助书籍,可能包含类似的内容。
最重要的一种推荐系统是基于协同过滤的方法。假设你认识一个和你品味相同的朋友,因为你们都热爱心理学,那么你可能会喜欢读其他你朋友读过但你没有读过的书。这是协同过滤背后的唯一概念。现在我们具体说说交替最小二乘法。
协同过滤可以通过矩阵分解技术轻松实现,如奇异值分解,其中用户评级矩阵被分解为用户概念矩阵、概念权重矩阵和评级概念矩阵。概念基本上是矩阵分解隐式生成的潜在或隐藏的因素,就像在书籍的情况下,不同的概念可以是心理学、数据科学、哲学等。
对于任何给定的项目,仅仅一个基本的点积就可以给出一个等级,但是这种方法有一个问题。
大多数矩阵分解技术,如奇异值分解,不知道如何处理不完整/稀疏矩阵,这意味着用户评级矩阵中有空值(这很常见,因为不是每个用户都读过所有的书)。传统上,工程师们在执行矩阵分解以进行协同过滤之前,一直用平均值或中值来输入这些值。这导致了过度拟合,因为从未被评级的书籍被均值或中值估算,这可能会使结果偏向它们。
最近的方法,如交替最小二乘法没有这些缺点。他们建议直接对观察到的评级进行建模,同时避免通过正则化模型进行过度拟合。为了学习因子向量(pu 和 qi),系统最小化已知评级集合上的正则化平方误差:
他们基本上试图像梯度下降一样将误差最小化。在上面的等式中,直觉是 r 是给定的评级,qi*pu 基本上是预测评级的矩阵(SVD 的结果)的点积,我们试图将其最小化,但最小化上面的等式还有另一个挑战。有两个未知数要最小化,所以这不是一个导致全局最小值的凸优化问题。
ALS 纠正了这个问题,同时也提供了一些其他的好处。ALS 技术在固定 qi 和固定 pu 之间轮换。当所有 pu 被固定时,系统通过解决最小二乘问题重新计算 qi,反之亦然。这确保了每一步都减少上述方程,直到收敛。这可能会导致算法的大规模并行化,因此可以在 Spark 中轻松实现。现在,让我们来谈谈好书的问题。
“好书”推荐系统
“好书”数据集包含了来自 T2 10000 本不同书籍的大约 100 万个评分。在大多数情况下,每个用户至少给10 本书打分,分数在 0 到 5 之间。我们将使用这 100 万个评分来预测所有其他没有被用户阅读的书籍的评分。此外,还有一个“to_read”数据集,其中包含用户保留以供未来阅读的书籍的详细信息(如 wishlist)。在建立我们的推荐系统后,我们将尝试预测这些“值得阅读”的书籍的评级,以了解我们的算法在这些书籍上的表现。因为用户已经希望阅读这些书籍,所以对这些要阅读的书籍的预测评级应该很高。
以下是数据集的链接:
一万本书,一百万收视率。还有标着要读的书,还有标签。
www.kaggle.com](https://www.kaggle.com/zygmunt/goodbooks-10k?select=sample_book.xml)
我将使用**‘data bricks’社区版**,因为它是在 spark 上运行 ML 的最佳平台,而且是免费的。
让我们看看收视率和书籍的数据框架。“评级”数据包含 book_id、user_id 和评级。图书数据集类似于所有 10000 本图书的元数据,包含图书名称、评级数量、book_id 和图书封面图像的 URL。我发现把书的封面印在“熊猫”数据框里很有趣。
评级数据集
图书数据集
因为我喜欢看书,所以我对这些书做了一些调查,根据它们的出版年份来了解这些书有多老,它们的平均评级是多少,哪些书的评级最高,哪些书的评论数量最多。
我看到《基督之前》中的一些书也和这本一样。吉尔伽美什的这部史诗出版的年份是公元前 1750 年。有意思。不是吗?
公元前 1750 年出版的书
我们来看看 1950 年以后的图书出版数量分布。
%sqlselect original_publication_year, count(*) as count from books where original_publication_year > 1950 group by original_publication_year
出版书籍数量的分布
1990 年后,每年似乎有 100 本书,然后每年都在增加,直到达到顶峰。
来看看收视率最多的书。
most_ratings = books_df.sort_values(by = ‘ratings_count’, ascending = False)[[‘original_title’,’ratings_count’, ‘average_rating’, ‘image_url’ ]][0:10]import pandas as pd
from IPython.display import Image, HTML
most_ratings[‘img_html’] = most_ratings[‘image_url’]\
.str.replace(
‘(.*)’,
‘<img src=”\\1" style=”max-height:124px;”></img>’
)
with pd.option_context(‘display.max_colwidth’, 10000):
display(HTML(most_ratings[[‘original_title’, ‘img_html’, ‘ratings_count’, ‘average_rating’ ]].to_html(escape=False)))
我们可以看到,收视率最高的书籍是《饥饿游戏》、《哈利·波特》等名著。
现在,让书籍按平均评分排名(不考虑评分的数量)。
high_rating_books = books_df.sort_values(by = ‘average_rating’, ascending = False)[[‘original_title’,’ratings_count’,’image_url’, ‘average_rating’ ]][0:10]high_rating_books[‘img_html’] = high_rating_books[‘image_url’]\
.str.replace(
‘(.*)’,
‘<img src=”\\1" style=”max-height:124px;”></img>’
)
with pd.option_context(‘display.max_colwidth’, 10000):
display(HTML(high_rating_books[[‘original_title’, ‘img_html’,’ratings_count’, ‘average_rating’ ]].to_html(escape=False)))
嗯,《加尔文与霍布斯全集》和《光辉之言》在 10000 本书中平均收视率最高。
来看看出书数量最多的作者:
authors_with_most_books = pd.DataFrame(books_df.authors.value_counts()[0:10]).reset_index()
authors_with_most_books.columns = [‘author’, ‘number_of_books’]
在数据集中,斯蒂芬·金有 60 本书是以他的名字命名的。
现在,让我们看看所有 10000 本书的平均评分分布:
import matplotlib.pyplot as plt
plt.figure(figsize=(12,6))
plt.title(‘Distribution of Average Ratings’)
books_df[‘average_rating’].hist()
displaay()
在评级“数据框架”中,大约有 100 万个评级,每本书大约有 100 个评级,这是一个非常公正的数据集。评分低于 100 的书很少。
现在,让我们看看用户评级矩阵的稀疏性,以了解如果我们不使用 ALS 而使用传统的 SVD 进行协同过滤,会产生多少偏差。
# Count the total number of ratings in the dataset
numerator = ratings.select(“rating”).count()# Count the number of distinct Id’s
num_users = ratings.select(“user_id”).distinct().count()
num_items = ratings.select(“book_id”).distinct().count()# Set the denominator equal to the number of users multiplied by the number of items
denominator = num_users * num_items# Divide the numerator by the denominator
sparsity = (1.0 — (numerator * 1.0)/ denominator) * 100
print(“The ratings dataframe is “, “%.2f” % sparsity + “% empty.”)
评级数据框 99.82%为空。
当然,不可能每个用户都给所有的书评分,因此最好只在原始评分的基础上建立一个算法。进入 ALS 世界。
算法-实现
现在,让我们将数据分割成训练集和测试集,使用交替最小二乘法进行协同过滤。
(training, test) = ratings.randomSplit([0.8, 0.2])
让我们导入 ALS 和回归评估器来查找 RMSE。
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.recommendation import ALS
from pyspark.sql import Rowals = ALS( userCol=”user_id”, itemCol=”book_id”, ratingCol=”rating”,
coldStartStrategy=”drop”, nonnegative = True, implicitPrefs = False)
implicitPrefs '参数在我们不使用像评级数据这样的显式数据时使用。有时,公司没有明确的数据,如评分,但仍然希望使用其他代理,如视图、点击、意愿列表等来构建推荐引擎。在这种情况下,使用了隐式偏好,但这超出了我们好书项目的范围。当我们没有用户的任何数据时使用 coldStartStrategy ,如果测试集上的用户在训练集中没有评级,这可能导致零预测。我们已经放弃了冷启动策略,因为我们想为我们手头的问题避免这种情况。
现在,让我们建立我们的超参数列表,然后在训练数据上拟合算法。
from pyspark.ml.tuning import ParamGridBuilder, CrossValidatorparam_grid = ParamGridBuilder() \
.addGrid(als.rank, [10, 50, 75, 100]) \
.addGrid(als.maxIter, [5, 50, 75, 100]) \
.addGrid(als.regParam, [.01, .05, .1, .15]) \
.build()# Define evaluator as RMSE
evaluator = RegressionEvaluator(metricName = “rmse”,
labelCol = “rating”,
predictionCol = “prediction”)
# Print length of evaluator
print (“Num models to be tested using param_grid: “, len(param_grid))
使用 param_grid 测试的模型数量:64
总共有 64 款车型将在我们收到最终车型之前进行测试和调试。并行化和 spark 的力量将使它变得非常快。
# Build cross validation using CrossValidator
cv = CrossValidator(estimator = als,
estimatorParamMaps = param_grid,
evaluator = evaluator,
numFolds = 5)model = als.fit(training)predictions = model.transform(test)predictions.show(n = 10)
测试集上的预测表明,它非常接近原始评级。例如,user_id 14372 的评分最初是 3,我们的算法预测它是 3.13,这非常接近。
rmse = evaluator.evaluate(predictions)
print(“Root-mean-square error = “ + str(rmse))
均方根误差= 0.913,平均误差为 0.9,即原始评级和预测评级之间的差值。现在,让我们为每个用户预测 10 本书和评级。
# Generate n recommendations for all users
ALS_recommendations = model.recommendForAllUsers(numItems = 10) # n — 10ALS_recommendations.show(n = 10)
# Temporary table
ALS_recommendations.registerTempTable("ALS_recs_temp")clean_recs = spark.sql("""SELECT user_id,
movieIds_and_ratings.book_id AS book_id,
movieIds_and_ratings.rating AS prediction
FROM ALS_recs_temp
LATERAL VIEW explode(recommendations) exploded_table
AS movieIds_and_ratings""")
clean_recs.show()
# Recommendations for unread books
(clean_recs.join(ratings, ["user_id", "book_id"], "left")
.filter(ratings.rating.isNull()).show())new_books = (clean_recs.join(ratings, ["user_id", "book_id"], "left")
.filter(ratings.rating.isNull()))
为每个用户推荐 10 本书。
现在,让我们使用’来 _read '数据(愿望列表数据),看看我们的算法会为愿望列表中的书预测什么。
这应该是非常好的,因为用户把它们保存在他们的愿望清单中。
recommendations = new_books.join(to_read,
on = [“user_id”, “book_id”],
how = “inner”)
print(recommendations.show())(recommendations
.withColumn('pred_trunc', recommendations.prediction.substr(1,1))
.groupby('pred_trunc')
.count()
.sort('pred_trunc')
.show())import matplotlib.pyplot as plt
plt.figure(figsize=(12,6))
plt.title('Most of the predicted ratings are above 3.8', fontsize = 12)
plt.suptitle('Distribution of predictedd ratings for the to_do lists', fontsize = 18)
rec = recommendations.toPandas()
rec['prediction'].hist()
display()
愿望清单中的大多数书籍都被预测为预期的 4.5 或更高版本。
你可以在这里访问我的代码:-
https://github.com/garodisk/Goodreads-recommendation-engine
非常感谢你的阅读!
参考文献:
一万本书,一百万收视率。还有标着要读的书,还有标签。
www.kaggle.com](https://www.kaggle.com/zygmunt/goodbooks-10k?select=sample_book.xml) [## 协同过滤
协同过滤通常用于推荐系统。这些技术旨在填补缺失的条目…
spark.apache.org](https://spark.apache.org/docs/2.2.0/ml-collaborative-filtering.html)
我的其他数据科学博客:-
语境多臂大盗——(网飞作品推荐背后的直觉)
用统计学家的大脑赌博。
用蒙特卡罗模拟法分析赌场的胜算
medium.com](https://medium.com/analytics-vidhya/gambling-with-a-statisticians-brain-ae4e0b854ca2)
使用 Spark 对 Instacart 的 300 万份订单进行购物篮分析
使用 Slash GraphQL 构建推荐引擎—第 2 部分
【TL;DR:在本系列的第二部分,我微调了最初的推荐引擎,添加了一个辅助数据源,并编写了一个 Angular 客户端来消费数据。]
在“使用 Spring Boot 和斜杠 GraphQL 构建推荐引擎”的文章中,最近发布的由 Dgraph 托管的斜杠 GraphQL 后端被用作基于 Java 的推荐引擎的记录系统。图形数据存储非常适合推荐引擎这样的用例,其中数据之间的关系与数据本身一样重要。使用 Slash GraphQL 允许我以最少的努力快速建立并运行一个完全托管的 GraphQL 数据库。
这篇文章还提供了关于如何利用 Slope One 系列的协同过滤算法,根据现有的评级来预测产品兴趣水平的见解。简而言之,Slope One 评级算法将是推荐引擎的核心。
文章最后通过在 Spring Boot 推荐引擎中提供 RESTful URIs 来提供来自存储在 Dgraph Slash GraphQL SaaS 服务产品中的数据的推荐。
本文将更进一步,介绍一个 Angular 客户端,以一种更容易使用的方式呈现这些数据……并希望得到赞赏。
Data Domain 的增强功能
虽然最初的文章展示了推荐引擎是如何工作的,但我觉得我的原始数据样本为一些样本客户提供了太多的评级。此外,我觉得我需要为第二个例子增加艺术家的数量。
因此,我清除了现有 Slash GraphQL 数据库中的数据,并重新开始。虽然它很容易更新,但在本练习中,底层模式不必更改,仍然如下所示:
type Artist {
name: String! @id @search(by: [hash, regexp])
ratings: [Rating] @hasInverse(field: about)
}type Customer {
username: String! @id @search(by: [hash, regexp])
ratings: [Rating] @hasInverse(field: by)
}type Rating {
id: ID!
about: Artist!
by: Customer!
score: Int @search
}
使用斜线 GraphQL 用户界面中的以下变化添加了新的Artist
项目列表:
mutation {
addArtist(input: [
{name: "Eric Johnson"},
{name: "Genesis"},
{name: "Journey"},
{name: "Led Zeppelin"},
{name: "Night Ranger"},
{name: "Rush"},
{name: "Tool"},
{name: "Triumph"},
{name: "Van Halen"},
{name: "Yes"}]) {
artist {
name
}
}
}
更新的Customer
记录也被插入:
mutation {
addCustomer(input: [
{username: "David"},
{username: "Doug"},
{username: "Jeff"},
{username: "John"},
{username: "Russell"},
{username: "Stacy"}]) {
customer {
username
}
}
使用与原始文章相同的突变,根据下表添加评级:
作者图片
H2(内存中)数据库简介
在第二篇文章中,我想介绍一个额外的数据源。这样做可以突出信息和事实通常来自多个数据源的事实。我决定使用H2——一个开源的、轻量级的内存 Java 数据库。使用以下 Maven 依赖项,可以快速轻松地将 H2 数据库添加到 Spring Boot:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
H2 数据库将为存储在 Slash GraphQL 中的Artist
记录提供附加信息。这些记录将被存储在一个名为Artists
的表中,主键就是Artist
的名字:
@Data
@Entity
@Table(name = "artists")public class Artist {
@Id
private String name;
private String yearFormed;
private boolean active;
private String imageUrl;
}
一个包含该表信息的data.sql
文件被创建并存储在 Spring Boot 存储库的 resources 文件夹中。因此,每次服务器启动时,都会填充 H2 数据库。
发展推荐引擎
为了在推荐引擎中看到价值,引擎提供的结果需要包括关于推荐的所有必要信息。为了满足这一需求,响应中建议的有效负载被更新为包含更多的Artist
属性,如下所示:
{
"matchedCustomer": {
"username": string
}, "recommendations": [
{
"name": string,
"yearFormed": string,
"active": boolean,
"imageUrl": string,
"rating": number,
"score": number
} ...
]
}
推荐引擎需要增强以接受两种额外形式的元数据:
- 当前选择的艺术家
- 当前客户
通过知道当前选择的艺术家,推荐引擎将知道排除对同一艺术家的任何推荐。此外,了解当前客户的能力避免了简单地随机挑选客户的需要。
介绍 Angular 客户端
为了快速创建客户端,我决定使用 Angular CLI 。Angular CLI 是一个命令行界面,允许您快速轻松地创建和细分组件、服务和基本功能,允许开发人员专注于编写业务逻辑以满足他们当前的需求。对我的技能来说,这是一个理想的选择。
在很短的时间内,我能够使用 Angular CLI 介绍以下项目:
- 与 Spring Boot 的艺术家、客户和推荐对象相关的服务
- list-artists 组件提供艺术家的简单列表
- view-artist 组件显示对活动客户和艺术家的推荐
因为有一个强大的 Angular 和npm
社区,我甚至能够通过几个命令和基本的配置更改,使用 angular-star-rating 和 css-star-rating 包来包含一个图形化的星级评定解决方案。当然, @ng-bootstrap 和 bootstrap 包也包括在内,这使得造型看起来更像样一些。
使用 Angular 客户端
配置好 Angular 客户端并运行 Spring Boot 推荐引擎后,可以使用以下 URL 来启动应用程序:
[http://localhost:4200](http://localhost:4200)
当应用程序加载时,将显示以下屏幕:
作者图片
该列表(来自列表艺术家组件)提供了来自 H2 数据库的信息和来自 Dgraph Slash GraphQL 数据库的平均评级。
单击 Rush 条目将调用 view-artist 组件,并显示如下所示的信息:
作者图片
在这种情况下,我选择 Russell 作为当前客户。在屏幕的顶部,显示了相同的信息,并且在右侧显示了乐队的图像。下面是来自 Spring Boot 服务中推荐 API 的信息。
结果迎合了名为 Russell 的客户,并有意避免为名为 Rush 的乐队做推荐。
如果客户更改为 Stacy,相同的屏幕会更新,如下所示:
作者图片
虽然屏幕上半部分的数据保持不变,但推荐部分完全不同,并迎合了新选择的用户。
结论
在本文中,推荐引擎被连接到一个客户端应用程序,并被进一步优化以提供比原始文章更多的价值。
虽然这个例子的设计非常简单,但是所采用的概念和方法可以被合并到一个功能完整的推荐引擎中。
使用 Dgraph 的斜线 GraphQL 和 Spring Boot 当然有助于非常短的上市时间。它们使得基于经验教训的原型设计、分析和采用新设计变得容易。
对于那些对完整源代码感兴趣的人,请查看斜线-图形-ql-angular GitLab 资源库。
(经原作者许可发表,约翰·维斯特)
构建动漫推荐系统
我已经决定做一个简单的动漫推荐系统。
背景资料
动漫是一种源自日本的手绘电脑动画,在世界各地吸引了大批追随者。动画产业由 430 多家公司组成。口袋妖怪和游戏王是西方电视上最受欢迎的动漫节目。由宫崎骏创作并由吉卜力工作室制作动画的《千与千寻》是动画电影中票房最高的一部。它在西方如此受欢迎的原因是宫崎骏的一个好朋友说服他将发行权卖给华特·迪士尼。像千与千寻,有数以千计的真正好的动画电影和节目是由同一家动画公司制作的。许多其他人可以以此为例,将其作为将此类艺术作品引入迪士尼+或西方任何流媒体网站的一种方式。这让我想到了我最近一直在做的事情:一个可以帮助任何人或任何公司查看/添加最高评级动漫的推荐系统。日本贸易振兴机构估计,2004 年该行业的海外销售额为 18 𝑏𝑖𝑙𝑙𝑖𝑜𝑛(仅美国就达 52 亿英镑)。这肯定已经增长,并有潜力进一步增长,尤其是在这个世界上的这段时间。像一些国家一样,日本正面临第一次长期衰退。
下面,你可以看到我对一个动漫推荐系统的逐步指导。这将有助于解决上述问题,并可以创造更多的需求动漫。它还可以帮助任何不熟悉小众流派的人快速找到收视率最高的项目。(帮助发展市场)。
我已经从https://www . ka ggle . com/Cooper union/anime-recommendations-database下载了动漫和用户评分。我将进行探索性的数据分析,让读者和我自己熟悉所呈现的数据。在那里,我使用奇异值分解(SVD)创建了一个基线模型。然后,我会做基于记忆的模型,将着眼于基于用户的 v 项目。我将使用 KNNBase、KNNBaseline 和 KNNWithMeans。然后我会选择表现最好的模型,评估其均方根误差(rmse)和平均绝对误差(mae)。
导入
import pandas as pd
import numpy as npimport random
from random import randintimport matplotlib.pyplot as plt
%matplotlib inline
import seaborn as snsfrom scipy.sparse import csc_matrix
from scipy.sparse.linalg import svdsfrom surprise.model_selection import train_test_splitfrom surprise.model_selection import GridSearchCV
from surprise.model_selection import cross_validatefrom surprise.prediction_algorithms import KNNWithMeans, KNNBasic, KNNBaselinefrom surprise.prediction_algorithms import knns
from surprise.prediction_algorithms import SVDfrom surprise.similarities import cosine, msd, pearsonfrom surprise import accuracyfrom surprise import Reader
from surprise import Dataset
在这个项目的大部分时间里,我决定坚持上面的方法,而且效果很好。我已经在 google colab 中尝试了下面的其他模型,我将在那里为导入库添加必要的代码。
擦洗/清洁
anime_df = pd.read_csv('./anime.csv')
anime_df.head()
形状看起来像什么?这很重要,因为它有助于查看任何空值
anime_df.shape
删除空值(如果存在)
anime_df.dropna(inplace=True)
我们将再次检查上述命令后的形状
anime_df.shape #this seemed to have reduced it down a bit
这实际上减少了我们的动漫数据框架!
在清理时,显示每一列代表什么是很重要的,这样我们就可以知道什么可以擦洗、转换,或者我们是否需要进行一些功能工程。
每列代表什么:
anime_id:每部动漫的 id 号片名
名称:电影片名
类型:类别
类型:描述动漫分成电视、电影、OVA 等 3 个类别
集数:总集数
评分:-1-10,最低到
接下来,它们和上面的匹配吗?
anime_df.info() #having a look at all of the columns and types from the above cell and current to remove#any unneccessary extraneous data
让我们看看这个推荐项目的第二个 csv 文件。
rating_df = pd.read_csv('./rating.csv')
rating_df.head()
让我们看看他们代表了什么。
每列代表什么:
user_id:不可识别的随机生成的 user-id
anime_id:用户已评级的动漫
评级:该用户已分配的 10 分中的评级(如果用户观看了该动漫但未分配评级,则为-1)
检查评级的形状
rating_df.shape
都有 anime_id。让我们将这两者结合起来,使事情变得容易得多。
df_merge = pd.merge(anime_df, rating_df, on = 'anime_id')
df_merge.head()
让我们再次检查形状。
df_merge.shape
我艰难地认识到奇异值分解(SVD)对如此大的数据集更敏感。我要看看最低评级。
df_merge.rating_x.min()
我已经决定去掉没有评级的列,它们表示为-1。在做推荐系统时,这取决于个人、任务和公司。我可能会在我的业余时间回去,把这个加回来,看看未完成的评级和它对推荐的影响。(确实起了巨大的作用)。我决定把它拿出来纯粹是为了娱乐。
df_merge = df_merge[df_merge.rating_y != -1]
df_merge.head()
现在,我再次检查形状,看看这减少了多少 SVD 数据。
df_merge.shape #have removed over 1 million rows
这对于免费的 google colab 来说仍然不够小。
sample = df_merge.sample(frac=.25)
sample.shape # this is still too large
我在我的 SVD 上运行了很多测试(这花了一天半的时间,我最终不得不满足于下面的样本大小)。
sample = df_merge.sample(n=5000)sample.shape #below I conduct SVD and it cannot handle anything larger than 5000 (i've tried)
让我们看看数据类型
sample.dtypes #rating_x needs to be an int, for it to work in ALS
让我们在这里转换它们
sample['rating_x'] = sample['rating_x'].astype(int)
可以在这里检查数据类型。从一开始,我就发现检查我的代码是非常重要的。当有调试问题时,它对消除问题很有帮助。
sample.dtypes
看起来评级数据框架是基于每个用户和他们对每个动画 id 的个人评级,而动画数据框架是来自其所有观众的平均总体评级。
#we are going to look at how many times each rating appears in a columnratings_x = sample['rating_x'].value_counts() #continuous
ratings_y = sample['rating_y'].value_counts() #discreteprint(ratings_x)
print(ratings_y)
另一件对 SVD 来说非常重要的事情是让变量变得谨慎,否则,它会占用你更多的时间。
sample.rating_x = sample.rating_x.apply(round)
sample.head()
太好了,我想我已经洗完了。让我们进入有趣的部分!
探索性数据分析
用户评级的分布
# plot distribution in matplotlib
ratings_sorted = sorted(list(zip(ratings_y.index, ratings_y)))
plt.bar([r[0] for r in ratings_sorted], [r[1] for r in ratings_sorted], color='cyan')
plt.xlabel("Rating")
plt.ylabel("# of Ratings")
plt.title("Distribution of Ratings")
plt.show()
我还没有把输出添加到清洗中,视觉效果非常重要,所以我会把它们添加进去。
这是我自己的图表,也可以在我的 github 上找到
#number of users
print("Number of Users:", df_merge.user_id.nunique()# print("Average Number of Reviews per User:", df_merge.shape[0])/df_merge.user_id.nunique()avg_rate_peruser = df_merge.shape[0]user = df_merge.user_id.nunique()
avg_num_review_per_user = avg_rate_peruser/userprint("Average Number of Reveiws per User:", avg_num_review_per_user)
用户数量:15382
每个用户的平均评论数:88.69
sample[‘user_id’].value_counts()
5000 的样本量,给了我们总共 3,381 个做过评论的用户。以上是整套的。
每个用户的评论数量
ratings_per_user = sample['user_id'].value_counts()
ratings_per_user = sorted(list(zip(ratings_per_user.index, ratings_per_user)))
plt.bar([r[0] for r in ratings_per_user], [r[1] for r in ratings_per_user], color='pink')
plt.xlabel('User IDs')
plt.ylabel('# of Reviews')
plt.title('Number of Reviews per User')
plt.show()
这是我自己的图表,也可以在我的 github 上找到
从我们的样本集来看,给出的很多分数都是 1 分和 2-4 分。
不同类型的动漫
print("Number of users:", sample.user_id.nunique())
print("Number of types of different anime:", sample.type.nunique())
print("Types of type:", sample.type.value_counts())
由此,我们可以看到有 6 种类型。它们是什么?
6 种类型:
TV 3492
Movie 666
OVA 461
Special 314
ONA 46
Music 21
Name: type, dtype: int64
OVA 代表原创视频动画
ONA 代表原创网络动画
特价是一次性的视频。
音乐是以动漫为主题的,通常有一个动画与之搭配,但它通常是一个非常短的视频。
点击率最高的动漫
# PLOT them
fig = plt.figure(figsize=(12,10))
sns.countplot(sample['type'], palette='gist_rainbow')
plt.title("Most Viewed Anime", fontsize=20)
plt.xlabel("Types", fontsize=20)
plt.ylabel("Number of Views with Reviews", fontsize = 20)
plt.legend(sample['type'])
plt.show()
这是我自己的图表,也可以在我的 github 上找到
让我们开始实施吧!
分析
最重要的是第一件事!做一个基础模型。我从转换我的数据开始。
#for surprise, it likes its data in a certain way and only that specific datadata = sample[['user_id', 'anime_id', 'rating_x']] #may need to do rating_x rounded and then use rating_yreader = Reader(line_format='user item rating', sep='')
anime_loaded_data = Dataset.load_from_df(data, reader)#train_test_split
trainset, testset = train_test_split(anime_loaded_data, test_size=.2)
确保您的数据格式正确
anime_loaded_data
接下来,我们将实例化。
#INSTANTIATE the SVD and fit only the train set
svd = SVD()svd.fit(trainset)
现在来看看预测和准确性。这对于比较您的学员模型非常重要。
predictions = svd.test(testset) #
accuracy.rmse(predictions)
这是我的基线:RMSE: 2.3128,2.3127
嗯,肯定不是 0 到 1 之间。这些分数实际上取决于领域。肯定不完美。我最初的反应是吓坏了,但事实证明 RMSE 超过 1 是没问题的。
我想看看我是否能减少这种情况。
#perform a gridsearch CV
params = {'n_factors': [20,50,100],
'reg_all': [.02,.05, .10]}gridsearch_svd1 = GridSearchCV(SVD, param_grid=params, n_jobs=-1, joblib_verbose=3)gridsearch_svd1.fit(anime_loaded_data)print(gridsearch_svd1.best_score)
print(gridsearch_svd1.best_params)
{‘rmse’: 2.3178,’ Mae ‘:2.2080 } { ’ RMSE ‘:{ ’ n _ factors ‘:20,’ reg_all’: 0.02},’ mae’: {‘n_factors’: 20,’ reg_all’: 0.02}}
它增加了。我现在将尝试其他模型,特别是基于内存的模型,然后是基于内容的模型。
k-最近邻(KNN)基本算法
#cross validate with KNNBasic
knn_basic = KNNBasic(sim_options={'name':'pearson', 'user_based':True}, verbose=True)
cv_knn_basic = cross_validate(knn_basic, anime_loaded_data, n_jobs=2)for i in cv_knn_basic.items():print(i)
print('-----------------')
print(np.mean(cv_knn_basic['test_rmse']))
将把你从其余的中拯救出来,这里是输出的一个简短剪辑
— — — — — — — — — 2.3178203641229667
您可以对均方距离(msd)进行同样的操作。
knn_basic_msd = KNNBasic(sim_options = {'name': 'msd', 'user-based':True})
cv_knn_basic_msd = cross_validate(knn_basic_msd, anime_loaded_data, n_jobs=2)for i in cv_knn_basic_msd.items():print(i)
print('-----------------')
print(np.mean(cv_knn_basic_msd['test_rmse']))
— — — — — — 2.31787540672289,得分较高。让我们试试另一个模型
KNNBaseline
#cross validate with KNN Baseline (pearson)
knn_baseline = KNNBaseline(sim_options={'name': 'pearson', 'user_based':True})
cv_knn_baseline = cross_validate(knn_baseline, anime_loaded_data, n_jobs=3)for i in cv_knn_baseline.items():print(i)
print('-----------------')
print(np.mean(cv_knn_baseline['test_rmse']))
— — — — — — — — — 2.317895626569356
同样,当我们希望它减少时,它却在增加。
KNN 基线与皮尔逊 _ 基线
knn_pearson_baseline = KNNBaseline(sim_options={'name': 'pearson_baseline', 'user_based':True})cv_knn_pearson_baseline = cross_validate(knn_pearson_baseline, anime_loaded_data, n_jobs=3)for i in cv_knn_pearson_baseline.items():print(i)
print('-------------------')
print(np.mean(cv_knn_pearson_baseline['test_rmse']))
这给了我们----- 2。46860 . 68868886861
KNNWithMeans
knn_means = KNNWithMeans(sim_options={'name': 'pearson', 'user_based': True})
cv_knn_means = cross_validate(knn_means, anime_loaded_data, n_jobs=3)for i in cv_knn_means.items():print(i)
print('------------')
print(np.mean(cv_knn_means['test_rmse']))
— — — — — — 2.3185632763331805
SVD 基线似乎具有最低的 RMSE。我会再次进行网格搜索
param_grid = {'n_factors': [5, 20, 100],
'n_epochs': [5,10],
'lr_all': [.002, .005],
'reg_all': [.02, .05, .5]}svd_gs = GridSearchCV(SVD, param_grid=param_grid, n_jobs=3, joblib_verbose=3)svd_gs.fit(anime_loaded_data)print(svd_gs.best_score)
print(svd_gs.best_params)
然后
#Now use this to fit test set, initial gridsearch was 2.77096, so will use that gs herehighest_perf_algo = gridsearch_svd1.best_estimator['rmse']#retrain the whole settrainset = anime_loaded_data.build_full_trainset()
highest_perf_algo.fit(trainset)#Biased Accuracy on trainset
predictions = highest_perf_algo.test(trainset.build_testset())print('Biased accuracy on Trainset', end='')
accuracy.rmse(predictions)#UnBiased Accuracy on testset
predictions = highest_perf_algo.test(testset)print('Unbiased Accuracy on test', end='')
accuracy.rmse(predictions)
我的结果是:训练集上的有偏精度 RMSE:2.3179301111112067,测试集上的无偏精度 RMSE:2.317938596
我做了一些合作模型,列在我的 Github repo 上。我的协作模型采用余弦相似度和基于用户的 v 项目。
基于内容
import nltk
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.corpus import stopwords
import string
from nltk import word_tokenize, FreqDist
import re
from sklearn.decomposition import TruncatedSVD
from sklearn.metrics.pairwise import linear_kernel
接下来,让我们提取必填字段。
genre_tag = sample.loc[:, ['anime_id','name','genre']]
让我们看看它是什么样子的
genre_tag.head()
目标是将每行的单词分开。
tags = {}
for col in ['genre']:
for a_id in sample['name'].unique():
for i in sample[sample['name'] == a_id][col]:
if a_id in tags:
tags[a_id].append(' '.join(i.lower().split('|')))
else:
tags[a_id] = i.lower().split('|')
将此转换成列表
tags_list = list(tags.values())
看看名单
tags_list[:5]
确保指定停用词为英语
stopwords_list = stopwords.words('english')
stopwords_list += list(string.punctuation)
目的是减少产生的噪音。
def process_article(article):
article = ' '. join(article)
tokens = word_tokenize(article)
tokens_2 = []
for token in tokens:
if token.lower() not in stopwords_list:
tokens_2.append(token.lower())
return tokens_2
让我们来处理这篇文章。这将把测试分成基于空格或撇号分隔的字符串列表
processed_tags = list(map(process_article, tags_list))
现在让我们从体裁中获取总词汇量。
articles_concat = []
for doc in processed_tags:
articles_concat += doc
每个单词出现多少次?
freqdist = FreqDist(articles_concat)
freqdist
让我们获得前 20 个单词,这将让我们看到最受欢迎的类型
most_common_genre = freqdist.most_common(20)
most_common_genre
让我们画出来
plt.figure(figsize=(22,12))
plt.bar([word[0] for word in most_common_genre], [word[1] for word in most_common_genre])
plt.title("Most Popular Genre", fontsize= 20)
plt.xlabel("Genre", fontsize=20)
plt.ylabel("Total Number of Times Genre is Present in Anime Data", fontsize=20)
plt.show()
这是我自己的图表,也可以在我的 github 上找到
我们的推荐系统可能会推荐喜剧、动作片、爱情片等。
现在,把它转换成矢量器
vectorizer = TfidfVectorizer(analyzer='word', ngram_range=(1,2), stop_words=stopwords_list)tags_list_2 = [‘ ‘.join(x) for x in tags_list]
tf_idf_data_train = vectorizer.fit_transform(tags_list_2)
接下来,让我们看看它如何符合标准。因为 NLTK 是一个完全不同的库,所以最好看看它解释的方差比。
#instantiate SVD
svd = TruncatedSVD(n_components=500, n_iter=7, random_state=42)#fit and transform the vectorized tf-idf matrix
tf_idf_data_train_svd = svd.fit_transform(tf_idf_data_train)
这里我们通过以下方式得到最终结果:
print(svd.explained_variance_ratio_.sum())
0.9962558733287571
在尝试了一堆模式之后,我觉得基于内容的模式是最好的。
在我的笔记本上,我还看着计算动漫和 TF-IDF 的相似度。
更多信息
基于所做的建模,最好是对来自相同动画工作室、作家、导演和媒体公司的收视率最高和收视率最高的动画进行更多的统计分析。从这里,我们可以调整推荐系统,以包括他们的最新发布。
另一个建议是看时间成分分析。电视节目是最受欢迎的动画类型,但是在一年中的什么时候呢?从这里开始,推荐系统可以帮助动画工作室填补财政年度内的空白。他们可以将日期分散开来,或者开辟一条新的渠道来推出更多特价商品。
参考资料:
[1]阿·库雷西,卡普斯顿 e (2020)
[2]苏珊·李,, (2018)
[3] Derrick Mwiti,如何用 Python 构建一个简单的推荐系统 (2018)
[4] Robi56,深度学习推荐系统 (2020)
[5] JJ、梅和 RMSE — 哪个指标更好? (2016)
[6]维基百科,动漫 (2020)
[7]维基百科,千与千寻 (2020)
[8]玛丽·马瓦德,冠状病毒:欧洲流媒体服务也在蓬勃发展 (2020)
建立网站规划的推荐系统
计算位置间相似性的多元方法
当涉及到场地规划应用时,组织强烈希望将来自不同来源的数据结合起来,这不仅是为了获得特定位置的见解,也是为了比较不同的地理位置,例如,为了找到显示相似特征的位置。
资料来源:CARTO
如果我们的目标是比较两个地理位置,我们可能需要考虑在该位置测量的各种数据点,既有来自“传统”来源(如人口普查)的,也有来自更现代来源的,如移动、金融交易和兴趣点(POI)数据。
在 CARTO,我们开发了一种方法来计算一组目标位置相对于现有站点的相似性得分,这可以证明是站点规划者在考虑开设、迁移或整合其站点网络时的一种重要工具。计算两个位置之间的相似性分数实际上归结为计算每个感兴趣的变量在这些位置的值之间的差异,或者在下文中称为“变量空间中的距离”,与地理距离相对。
为了估计两个位置之间的相似性,我们首先需要定义“位置”的含义。例如,一些数据可能在点一级可用(例如,POI 数据),而其他数据可能已经在空间上聚合(例如,人口普查数据通常在给定的行政单位一级可用),并且不同的数据源可能在不同的空间分辨率下可用。因此,比较不同的位置需要首先定义一个共同的空间支持。例如,我们可以使用一个使用四叉树数据结构构建的规则网格,其中每个网格单元都由一个称为四叉树键(quadtree keys,简称“quadkeys”)的一维位串唯一定义。然后(这是难点),我们需要将每个数据源转换到这个公共空间支持。出于本帖的目的,我们将求助于一个基于面积插值的快速(尽管不是最优)解决方案,它使用原始和公共支持面之间的相交面积作为权重来重新分配数据。
下面的地图显示了美国人口普查中通过面插值转换为四键格网的总人口示例:
资料来源:CARTO
共享工作空间公司基于计算相似性的选址示例
现在,假设您可以访问从我们的数据观测站转换到四键网格的第三方和制图数据,并且您想要使用这些数据计算不同位置(网格点)之间的相似性得分。例如,假设共享工作空间行业的市场领导者希望使用更多数据来推动其快速扩张战略。
假设我们想考虑这家公司在纽约的站点,结合人口统计、财务和 POI 数据来查找在洛杉矶具有相似特征的位置。这种相似性分析的结果将指出他们应该计划开设新空间/重新安置与纽约市目标位置具有相似特征的现有空间的最佳地点。
第一步是选择我们希望包含在分析中的相关变量。这里,我们将使用人口统计、POI 和财务数据的组合。然后,如上所述,目标是计算可变空间中的(一对一)距离:
在选定的原始单元格(I)和每个目标单元格(f)之间,其中索引 j 标识变量类型(例如,总人口)。对于这个用例,这里的目标区域是洛杉矶的整个城市。
虽然这种方法看起来很简单,但是为了正确计算距离,我们需要考虑一些缺点。
当变量有不同的方差时会发生什么?
分析中所选变量的箱线图。注意 y 轴上的对数刻度。来源:CARTO
很明显,在这种情况下,总人口和食物中毒人数中的距离将被赋予不同的权重。为了说明不同的方差,数据被标准化,使得每个变量的均值和单位方差为零。这意味着在计算距离时,所有变量都被赋予相同的权重。
当变量之间存在相关性时会发生什么?
当存在相关变量时,在距离的计算中存在大量冗余信息。通过在距离计算中考虑变量之间的相关性,我们可以消除冗余。为了理解相关性是如何混淆距离计算的,我们可以查看下图,该图显示了一组呈现正相关性的点。
相关性对距离计算影响的示意图。来源:CARTO
观察该图,我们知道蓝点比绿点更有可能与原点的红点属于同一簇,尽管它们与中心的距离相等。很明显,当有相关变量时,我们需要在距离计算中考虑相关性。
本分析中包含的变量的相关矩阵。矩阵中的每个元素代表皮尔逊相关系数,它是两个变量之间线性相关性的度量。来源:CARTO
正如预期的那样,上图显示了我们考虑的一些变量表现出相关性(例如,收入中值与租金中值正相关,所有财务变量彼此之间正相关)。为了考虑相关变量,距离不是在原始变量空间中计算的,而是在变量线性不相关的主成分空间(PCA)中计算的。
简单来说 PCA
主成分分析 (PCA)是一种将高维向量的数据集转换成低维向量的技术。例如,这对于特征提取和降噪是有用的。PCA 找到数据向量的较小维度的线性表示,使得原始数据可以从具有最小平方误差的压缩表示中重构。最流行的 PCA 方法是基于样本协方差矩阵的特征分解
其中 Y 是居中的(逐行零经验平均值)n×d 数据矩阵,其中 d 是数据点的数量,n 是变量的数量。在计算协方差矩阵的特征向量( U )和特征值( D )矩阵后
并按照特征值递减的顺序重新排列它们的列,主分量(PC 或因子)计算如下
其中 P 表示对应于最大 P 个特征值的特征向量,即对应于最大数量的解释方差。然后,原始(居中)数据可以重建为
这里,为了说明保留的 PC 数量的不确定性,我们创建了一个距离计算集合,对于每个集合成员,我们在由分别解释 90%和 100%方差的 PC 数量定义的范围内随机设置保留的 PC 组件的数量。
在缺失数据的情况下,我们如何计算距离?
不幸的是,PCA 对缺失数据不起作用。如果缺失数据是常见的,那么普通的内插或均值插补解决方案并不是一个好主意。
此分析中包含的每个变量的缺失数据部分。资料来源:CARTO
当给定位置的 POI 变量缺失时,我们可以有把握地假设该位置中该变量的 POI 数量为零。另一方面,对于其他变量(如金融变量),我们需要找到一种稳健的方法来处理缺失值。
为了考虑缺失数据,我们在这里使用一种概率方法进行 PCA,称为概率 PCA ,它使用一种迭代程序来交替插入缺失数据并更新不相关的分量,直到收敛。
PPCA 一言以蔽之
在 PPCA,完整数据通过生成潜在变量模型建模,该模型迭代更新预期完整数据对数似然和参数的最大似然估计。PPCA 还具有比 PCA 更快的优点,因为它不需要计算数据协方差矩阵的特征分解。
PCA 也可以被描述为概率潜变量模型的最大似然解,它被称为 PPCA:
随着
假设主分量 Z 和噪声ε都是正态分布的。可以通过使用期望最大化(EM)算法找到模型参数的最大似然(ML)估计来识别模型。EM 是用于学习具有不完全数据的参数的通用框架,其迭代地更新参数的期望完全数据对数似然和最大似然估计。在 PPCA,数据是不完整的,因为没有观察到主要成分 Z 。当缺失数据存在时,在 E-步骤中,根据给定的观察变量,对未观察变量的条件分布进行完全数据对数似然的期望。在这种情况下,更新 EM 规则如下。
电子步骤:
m 步:
其中 P 和 Z* 的每一行仅基于 Z* 的那些列来重新计算,这些列有助于数据矩阵的相应行中的观测值的重建。
到目前为止,我们计算了可变空间中纽约给定共享工作空间位置和洛杉矶目标区域中每个位置之间的距离。然后,我们可以选择距离最短的目标位置作为在洛杉矶开设/搬迁办公室的最佳候选位置。
然而,我们如何知道距离足够小,或者我们如何定义相似性?
为了回答这个问题,我们通过将每个目标位置的得分与平均向量数据的得分进行比较,定义了相似性技能得分(SS)
其中分数仅代表可变空间中的距离,即
如果我们通过系综生成来考虑每个目标位置的距离计算中的不确定性,则每个目标位置的得分变成
其中 K 是系综成员的数量。
- 当且仅当目标位置比平均向量数据更类似于原点时,技能得分(SS)将为正
- 在该评分规则下,具有较大 SS 的目标位置将更类似于原点
然后,我们可以按照 SS 的降序对目标位置进行排序,并且只保留满足阈值条件的结果(SS = 1 表示完全匹配或零距离)。有关评分规则的更多信息,请参见费罗(2014) 。
将这一切结合在一起
下面我们可以看到纽约市两个原点位置的目标区域的相似性技能(SS)得分图,这两个原点位置目前有共享工作区,一个在曼哈顿,一个在布鲁克林。从该图中,我们可以看到,该方法为曼哈顿和布鲁克林办公室选择了不同的目标位置,这反映了起始位置变量之间的差异。特别是,对于曼哈顿办事处,洛杉矶的选定地点集中在贝弗利山和西好莱坞地区,在洛杉矶的所有不同地区中,这些地区在人口统计、金融和 POI 数据方面与曼哈顿最为“相似”。另一方面,根据布鲁克林的 SS 评分规则,被选为最相似的地点更加分散,这表明我们的示例公司在决定开设与布鲁克林具有相似特征的新办事处时,可能会考虑洛杉矶的不同街区。
资料来源:CARTO
本文原载于 CARTO 博客 。
特别感谢Mamata Akella,CARTO 的制图主管,对她在本帖地图创作中的支持。
用 Spark ML 和 Elasticsearch 构建推荐系统
使用交替最小二乘法和余弦相似度的产品推荐
作者图片
在不同产品公司的搜索团队工作时,总是有为用户建立推荐系统的持续需求。我们与流行的搜索引擎如 Elasticsearch、SOLR 建立了合作关系,并试图完善搜索相关性。但是当涉及到搜索相关性和仅仅用搜索引擎创建推荐系统时,总是有一个限制。我总是惊讶于亚马逊和网飞这样的公司根据我们的兴趣提供推荐。因此,通过使用 Apache spark 机器学习功能和 elasticsearch,我们将建立一个推荐系统。
如上图,如果用户 1 喜欢物品 A、物品 B、物品 C,用户 2 喜欢物品 B、物品 C,那么有很大概率用户 2 也喜欢物品 A,可以向用户 2 推荐物品 A。
在推荐系统领域,主要有三种用于提供推荐的技术。
- 协同过滤
- 基于内容
- 混合技术
我们将使用 Pyspark 中的协同过滤技术来创建一个推荐系统。Apache Spark ML 实现了用于协同过滤的交替最小二乘法(ALS ),这是一种非常流行的推荐算法。
ALS 是一种矩阵分解算法,使用交替最小二乘法和加权λ正则化(ALS-WR)。
Spark ALS 的主要问题是,它只会为特定用户(用户-产品模型)推荐顶级产品,为特定产品(产品-用户模型)推荐顶级用户。
计算所有配对的相似性并不是针对大的产品目录。O(n2)组合数量的增长很快导致了代价过高的混洗操作和不可行的计算机时间。为了计算项目-项目相似性,我们使用来自 ALS 模型的项目因素的相似性,使用 spark 和 elasticsearch 建立了项目-项目模型。
ALS 算法本质上分解两个矩阵——一个是用户特征矩阵,另一个是项目特征矩阵。我们正在对项目特征等级矩阵进行余弦相似性运算,以找到项目间的相似性。
关于数据集
数据集主要包含来自 Amazon.com 的两种类型的数据。一个是元数据,它包含产品元数据,另一个是用户各自的不同产品的评级数据。
注:数据集可以从这个 站点 下载。
使用的 spark 版本是 2.4.6,elasticsearch 版本是 7.9.0
工作流程
作者图片
- 将产品数据集加载到 Spark 中。
- 使用 Spark DataFrame 操作来清理数据集。
- 将清理后的数据加载到 Elasticsearch 中。
- 使用 Spark MLlib,从评级数据中训练一个协同过滤推荐模型。
- 将生成的模型数据保存到 Elasticsearch 中。
- 使用弹性搜索查询,生成推荐。
加载数据
以下代码用于读取产品数据,并将其转换为 spark 数据帧。
在 spark 中加载数据后,数据帧如下所示。
作者图片
现在我们需要将产品数据索引到一个 Elasticsearch 索引中。为了将 elasticsearch 与 spark 连接起来,我们需要在类路径中添加一个 JAR 文件,或者将该文件放在 spark 的 jars 文件夹中。JAR 文件需要基于我们现有的 spark 版本下载。
既然已经在 Elasticsearch 中索引了产品元数据,我们需要根据主 id 索引文档的 model_factor 列中每个产品的特征向量。
准备特征向量
特征向量是通过使用 ItemFeatures 矩阵生成的,该矩阵是在我们对用户产品评级数据运行 MLib ALS 算法时生成的。
在 spark 中加载评级数据并转换为数据帧后,将如下所示。
作者图片
在对模型进行评估后,我们得到了一个均方根误差值 RMSE=0.032,这是相当不错的。值越低,模型越好。
现在我们必须将项目向量索引回 Elasticsearch。
现在项目向量已被索引到 elasticsearch,我们需要找到类似的产品。
为了找到两个项目之间的相似性,我们将使用余弦相似性功能。余弦相似性是内积空间的两个非零向量之间相似性的度量,度量它们之间角度的余弦。
作者图片
余弦距离值越小,项目越相似。
我们将使用 elasticsearch 中的 script_score 功能计算产品的相似性得分。余弦相似度函数计算给定查询向量和文档向量之间的余弦相似度。
以下查询已用于从 elasticsearch 获取类似的项目。我们用类别过滤也是为了在类别中得到更好的结果。我们可以根据需要更改过滤器选项。该脚本将余弦相似度加 1.0,以防止分数为负。为了利用脚本优化,提供一个查询向量作为脚本参数。
以下代码已被用于获得类似的产品。我们正在传递产品的 ASIN 和我们想要的产品推荐数量。
display_similar('B001A5HT94', num=5)
这将给出前 5 名产品的结果,这与我们已经通过的产品类似。
作者图片
还不错!结果与我们搜索的产品相似。我们可以随时调整 ALS 算法,以获得更好的性能和精度。
我们也可以采用隐式反馈参数来调整算法。本质上,这种方法不是试图直接对评级矩阵建模,而是将数据视为代表用户行为观察中的强度的数字(如点击次数或购买产品的次数)。
源代码可以在 Github 上找到。
参考
- 协同过滤-https://spark . Apache . org/docs/3 . 0 . 0/ml-collaborative-filtering . html
- 使用标签较远的评论和细化的方面来证明推荐的合理性。简墨倪,李嘉诚,朱利安麦考利。自然语言处理中的经验方法(EMNLP),2019 ( PDF )
为标记音乐建立推荐系统
找出哪些标签描述了相似的音乐!
介绍
最近,我在听一个关于音乐行业人工智能的播客。当我们想到人工智能和音乐时,首先想到的通常是音乐分类和生成。然而,在本期播客中,《爱与数据》的亚历克斯·雅各比用一种完全不同的方式让我大吃一惊。和他的团队一起,他开发了一个 ML 模型,让媒体制作者为他们的广告、电影等找到合适的音乐。更有效率。当他们搜索一些关键词时,该模型还会向他们推荐没有任何关键词标记的音乐,但这些关键词与他们搜索的关键词有某种关联。这是天才!
想象一下,你错过了多少机会,查找书籍、学术文献、编程教程或猫视频的“错误”关键词。我觉得它令人惊叹。如果我们这样来看,标签是一种平台设置下,厂商和需求者之间的一种沟通方式。如果我们想在亚马逊上购买新袜子,搜索过程相当简单,因为我们可以口头描述我们感兴趣的对象。然而,在音乐的例子中,我们需要依靠标题和标签来描述我们寻找或提供的产品。因此,好的标签的重要性,即有效的标签将供应商和需求者聚集在一起,怎么强调都不为过!只有两种方法可以改进对带有标签的内容的搜索。
- 找到解决不良标签的方法
- 寻找使用更好标签的方法
亚历克斯·雅各比的工作是迈向第一步的重要一步。我想向后者迈出一小步,帮助创作者和媒体制作人通过更好的标签找到一起。我想建立一个推荐系统,计算标签之间的相似性,并使用这些来进行推荐。有很多模型是根据单词之间的关系来训练的。这些模型将能够推荐与前一个标签相匹配的标签。但音乐是一种非常特殊的东西,你是否能从博客帖子或聊天上训练的数据集得出好的音乐标记的结论是值得怀疑的。让创作者成为媒体经理要搜索的推荐标签,让媒体经理成为音乐人实际使用的推荐标签,会更有帮助。这是(现在仍然是)这个项目的目标!
我将遵循基于内容的过滤策略,计算标签之间的相似度。图 1 显示了这个项目的基本步骤。一切都将在 Python 中完成。
图 1 —这个项目的 3 个步骤
步骤 1 —收集数据
首先,我需要一个数据集。我真正需要的所有信息是许多音乐片段的标签数组。可能一首歌的标签是 t1 = ["funky “,” groove “,” guitar “,” uplifying "],另一首歌的标签是 t2 = ["kids “,” happy “,” uplifying “,guitar”]。我的推荐系统应该发现“振奋”和“吉他”在某种意义上是相似的,因为它们经常一起用来描述一首音乐。因此,我想从网上搜集大量曲目的标签列表。我找到了网站www.bensound.com,,它提供免费的电影、商业或博客音乐。图 2 显示了 bensound 主页的外观。有超过 20 页,每一页有 12 个音轨。如果我可以遍历所有页面,打开该页面上的每个轨道并提取其标签,我就有了一个不错的数据集。
图 2-www.bensound.com 主页
[## MaxHilsdorf/music _ tag _ recommender
permalink dissolve GitHub 是 4000 多万开发人员的家园,他们一起工作来托管和审查代码,管理…
github.com](https://github.com/MaxHilsdorf/music_tag_recommender/blob/master/webscraping.ipynb)
您可以在这个 github 资源库中找到整个项目的注释 jupyter 笔记本以及所有数据文件。在本文中,我将只使用代码片段。
我用“请求”和 bs4 的“美汤”来刮数据。你将在本章中找到的代码在被解析的网页内容中做了大量的实验。可能有些数字或命令对您来说没有意义,但我希望我的函数的总体结构是清楚的。
首先,我编写了一个函数来获取任何页面上的所有跟踪 URL。
def get_tracks(page_url):
# Setup soup for main page
page_r = requests.get(page_url)
page_soup = BeautifulSoup(page_r.text, "html.parser")
page_html = page_soup.body.div.find_all("a")
# Setup empty list and fill it with the URLS
track_list = []
for i in range(33,84, 5): # these are the positions where we find the track urls in this case
entry = page_html[i]["href"]
track_list.append(entry)
return track_list
现在我有了一些 track urls,我需要一个函数来从中提取标签。
def get_tags(track_url):
# Setup soup for track page
track_r = requests.get(track_url)
track_soup = BeautifulSoup(track_r.text, "html.parser")
taglist_html = track_soup.body.div.find("p", {"class": "taglist"})
taglist_html = taglist_html.find_all("a")
taglist = []
for i in taglist_html:
for j in i:
taglist.append(j)
return taglist
最后,我结合了上面的两个函数,遍历所有页面并从所有音轨中提取所有标签。幸运的是,不同页面的 url 仅在结尾有所不同:
- https://www.bensound.com/royalty-free-music
- 【https://www.bensound.com/royalty-free-music/2
- https://www.bensound.com/royalty-free-music/3
- […]
这使得遍历所有页面变得更加容易,因为它可以用以下 4 行代码来处理:
url_template = “[https://www.bensound.com/royalty-free-music/{page](https://www.bensound.com/royalty-free-music/{page)}"page_urls = ["[https://www.bensound.com/royalty-free-music/](https://www.bensound.com/royalty-free-music/)"]
for i in range(2,24):
page_urls.append(url_template.format(page = i))
这是抓取标签的最后一个功能:
def get_all_tags(page_urls):
# Setup the dict that we will create the df from later
tag_dict = {"URL" : [], "Tags" : []}
# Loop through all page urls and extract the track urls
for i, page_url in enumerate(page_urls):
track_list = get_tracks(page_url)
# Loop through the track urls and extract the revleant information
for track_url in track_list:
tag_list = get_tags(track_url)
tag_dict["URL"].append(track_url)
tag_dict["Tags"].append(tag_list)
return tag_dict
当我将 tag_dict 转换成熊猫数据帧时,它看起来像这样:
表 1 —原始数据帧
数据都在那里,有超过 250 行的标记列表。当然还是很乱,需要一些清理。
步骤 2 —准备数据
表 1 中的数据存在一些问题。在我意识到必须转换数据帧的基本结构之前,我尝试了很多清理技术。“标签”栏由列表组成,熊猫不适合这些列表。对于我想做的每一次计算,可以遍历每个列表中的每一项,但这绝对不节省时间。我想以某种方式转换数据帧,这样我就可以利用熊猫基于矢量的计算,这将节省我很多时间!
在转换之前,我仍然需要做一些清理工作,我将在这里总结一下,但不展示实际的代码。如果你感兴趣的话,github 代表会告诉你一切。
- 从 url 中提取曲目名称
- 将“标签”列中的值从字符串转换为列表对象
- 从标记中删除任何“\xa0”值(在 Latin1 中表示空格)
我的计划是创建一个基于布尔的数据帧,所有标签为列,所有轨道为行。在任何列中,轨道将具有 False 或 True 值,这取决于标签(列)是否在轨道标签列表中。这将使数据帧变得更大(从 500 到 200,000 个数据点),但之后将允许有效的矢量化操作。转换后,数据帧应如下所示:
Track | Tag 1 | Tag 2 | Tag 3 | Tag 4
Track 1 | False | False | False | True
Track 2 | True | False | True | True
Track 3 | True | True | False | False
为此,我需要获得所有惟一标签的列表,这将是新数据帧中的列。
def get_all_unique(dataframe_of_lists):
# Create empty list
unique_tags = []
# Loop through the dataframe rows (lists) and each item inside
for row in dataframe_of_lists:
for item in row:
# Add item to all_tags if it's not already in it
if item not in unique_tags:
unique_tags.append(item)
return unique_tags
通过这个函数,我创建了实际的数据帧:
def create_boolean_df(unique_tags, track_taglists): # Create new df with a column for every tag
boolean_df = pd.DataFrame(columns = unique_tags)
# Create an empty dict
data_dict = {}
# Loop through the columns (tags) in the boolean_df and add them to the dict
for col in boolean_df:
data_dict[col] = []
# Loop through the taglists in the old dataframe
for taglist in track_taglists:
# Check if the column (tag) is in the tracks taglist. If yes append True else append False
data_dict[col].append(col in taglist)
# Use the boolean lists as values for the boolean_df's columns
for col in boolean_df:
boolean_df[col] = data_dict[col]
return boolean_df
表 2 —布尔数据框架
第三步——建立推荐系统
第 3.1 步—数学
所以我准备好了数据。现在我需要我的推荐系统的底层算法。一种方法是使用条件概率。如果我已经标记了 t1 =“提升”,算法将为数据集中的所有标记 ti 计算 P(ti / t1)。这是假设 t1 已经被标记,您在 tracks 标记列表中找到标记 ti 的概率。这也意味着 P(ti/t1)!= P(t1/ti)。虽然这种方法对于标签推荐来说是最准确的,但是它相当复杂。
我在寻找一种解决方案,它能让我事半功倍。相关性呢?从技术上讲,您不能像我们的标签那样关联分类变量,但是您可以计算关联,这是一种有点类似的度量。Association 将返回介于 0 和 1 之间的值,表示在同一标记列表中找到 tag1 和 tag2 的概率,前提是它们中至少有一个存在。这将是一个更容易和更快的计算,但这将意味着没有条件概率包括在内。任何标签 t1 和 t2 被一起标记的概率将被假定是相同的,不管是 t1 还是 t2 先被标记。一般来说,我们不认为标记是一个连续的过程,而是一个预先确定的适合标记列表的执行。我们会认为,在输入第一个标签之前,就已经确定了一段音乐的最佳标签。出于这个原因,我坚持使用相关的方法,让每个人的生活更轻松。
在下面的代码中,您会发现我的相关性度量的原型:
def correlation_dummy(a_and_b,
a_not_b,
b_not_a):
# Formula for probability: positive_outcomes / possible_outcomes
positive_outcomes = a_and_b
possible_outcomes = a_and_b + a_not_b + b_not_a
# r is the number of cases where a and b are tagged relative to the number of cases where only a or only b is tagged
r = positive_outcomes / possible_outcomes
return r
最终的函数如下所示:
def correlate_with_every_tag(df, tag_a, dict_mode = True):
unique_tags = list(df.columns)
# In dict_mode, the results are stored in a dict, which is good for analyzing one tag
# However, in order to transform the data into a df later, we need a list output
if dict_mode:
# Loop through every tag and store the correlation in the dict
correlation_dict = {}
for tag_b in unique_tags:
correlation_dict[tag_b] = correlation(df, tag_a, tag_b)
return correlation_dict
else:
# Loop through every tag and store the correlation in a list
correlation_list = []
for tag_b in unique_tags:
correlation_list.append(correlation(df, tag_a, tag_b))
return correlation_list
在这里,我创建了一个字典,所有标签作为键,它们与其他标签的所有相关性作为值。
unique_tags = list(music_tags.columns)correlation_matrix_dict = {}for tag_a in unique_tags:
correlation_matrix_dict[tag_a] = correlate_with_every_tag(music_tags, tag_a, dict_mode = False)
这个计算差点弄坏了我的电脑,但是在它折磨完我之后,它返回了这个数据帧:
表 3 —相关矩阵
有 250 个样本,从 500 到 900,000 个数据点。升级很快。
第 3.2 步结果
现在,你当然想看到结果!从这么小的样本中你能得到多好的标签推荐?
我编写了几个函数来选择与输入标签最匹配(相关性最高)的标签。有关所有功能,请参见 github rep。
def get_recommendations(df, tag, num_of_recommendations):
corr_df = find_correlations(df, tag)
recommendations_df = find_highest_correlations(corr_df, num_of_recommendations)
print("Recommendations:", list(recommendations_df["tag"]))
此时,您可以输入一个标签并接收三个最佳推荐。
以下是一些常见起始标签的建议:
- 快乐:有趣、感觉良好、积极
- 悲伤的:感人的,感人的,忧郁的
- 精力充沛:能源、运动、电力
让我们尝试几个不常用的标签:
- 侵略性:西方,饱和,滑行# lol
- 侵略性(!):沉重、坚硬、极端
- 放松的:寒冷、平静、悠闲
- 戏剧类:电影、配乐、电影
- 平滑:放松,嘻哈,装饰
这些推荐有的真的很好!如果我现在正在标记我的音乐,我会发现这些真的很有帮助。然而,我们也确实看到了一些问题。显然,单词“aggressive”在 bensound 上通常被拼写为“aggressive ”,这就是为什么“aggressive”比正确拼写的版本得到更好的推荐。这是一个问题。对“平滑”的推荐看起来也不太好。这可能是由于样本量小,因为“平滑”没有足够的标签来计算可靠的相关性。总的来说,所有迹象都指向更多的数据!在我开始将这个推荐系统转化为实际可用的产品之前,我需要收集更多的数据,并希望对更不常见的标签获得更好的推荐。
在这篇文章的结尾,我将使用我的推荐系统为一首歌挑选尽可能多的合适标签。让我们使用互联网上最受欢迎的“全明星”由 Smash Mouth。我将从一个标签开始,从推荐给我的三个标签中选择最合适的标签。然后我将使用这个标签作为新的输入。
标签号:输入标签→输出 1、输出 2、输出 3 →输出选择
标签 1:“积极”→“光明”、“向上”、“广告”→“向上”
标签二:“振奋”→“流行”,“光明”,“激励”→“动机”
标签三:“动机”→“企业”,“成功”,“商业”→“成功”
标签四:“成功”→“企业”、“商业”、“励志”→“励志”
标签 5:“激励”→“企业”、“业务”、“演示”→停止
好的,我们有“积极的”、“振奋的”、“激励的”、“成功的”和“激励的”。现在,我们碰壁了。然而,这首歌的标签列表还不错。毕竟,推荐系统不是为自己做标记而构建的,而是为了支持人类做出创造性的标记决定。
接下来,我将把我的样本增加到 500 首左右,看看会发生什么。当这些完成后,我肯定会再写一篇博文!
感谢大家的阅读!
在喀拉斯建立一个 ResNet
利用 Keras 函数 API 构造残差神经网络
什么是残差神经网络?
原则上,神经网络应该得到更好的结果,因为它们有更多的层。一个更深层次的网络可以学到比它更浅层次的网络所能学到的任何东西,而且(可能)不止这些。对于给定的数据集,如果网络无法通过向其添加更多图层来了解更多信息,那么它只需了解这些附加图层的身份映射。这样,它保留了前几层的信息,不会比更浅的层差。如果找不到比这更好的东西,网络至少应该能够学习身份映射。
但实际上,事情并不是这样的。更深的网络更难优化。网络每增加一层,训练的难度就会增加;我们用来寻找正确参数的优化算法变得更加困难。随着我们添加更多的层,网络得到更好的结果,直到某一点;然后,随着我们继续添加额外的层,精度开始下降。
残余网络试图通过增加所谓的跳跃连接来解决这个问题。上图中描绘了一个跳过连接。正如我之前所说,更深层次的网络至少应该能够学习身份映射;这就是 skip 连接所做的:它们添加从网络中的一个点到一个转发点的身份映射,然后让网络学习额外的𝐹(𝑥).如果网络没有更多可以学习的东西,那么它就将𝐹(𝑥学习为 0。原来网络比身份映射更容易学习到更接近 0 的映射。
如上图所示的具有跳跃连接的块被称为残差块,残差神经网络(ResNet)就是这些块的串联。
一个有趣的事实是,我们的大脑具有类似于残余网络的结构,例如,皮层第六层神经元从第一层获得输入,跳过中间层。
Keras 函数式 API 简介
如果你正在阅读这篇文章,你可能已经熟悉了顺序类,它允许你通过一层一层地堆叠来轻松地构建神经网络,就像这样:
**from** keras.models **import** Sequential
**from** keras.layers **import** Dense, Activation
model **=** Sequential([
Dense(32, input_shape**=**(784,)),
Activation('relu'),
Dense(10),
Activation('softmax'),
])
但是这种建立神经网络的方式不足以满足我们的需求。对于顺序类,我们不能添加跳过连接。Keras 也有 Model 类,它可以和 functional API 一起使用来创建层,以构建更复杂的网络架构。
构造时,类keras.layers.Input
返回一个张量对象。Keras 中的层对象也可以像函数一样使用,用张量对象作为参数调用它。返回的对象是一个张量,然后可以作为输入传递给另一个层,依此类推。
举个例子:
**from** keras.layers **import** Input, Dense
**from** keras.models **import** Model
inputs **=** Input(shape**=**(784,))
output_1 **=** Dense(64, activation**=**'relu')(inputs)
output_2 **=** Dense(64, activation**=**'relu')(output_1)
predictions **=** Dense(10, activation**=**'softmax')(output_2)
model **=** Model(inputs**=**inputs, outputs**=**predictions)
model.compile(optimizer**=**'adam',
loss**=**'categorical_crossentropy',
metrics**=**['accuracy'])
model.fit(data, labels)
但是上面的代码仍然构建了一个有序的网络,所以到目前为止还没有真正使用这种奇特的函数语法。这种语法的真正用途是在使用所谓的合并层时,通过它可以合并更多的输入张量。这些层的几个例子是:Add
、Subtract
、Multiply
、Average
。我们在建造剩余的积木时需要的是Add
。
使用Add
的例子:
**from** keras.layers **import** Input, Dense, Add
**from** keras.models **import** Model
input1 **=** Input(shape**=**(16,))
x1 **=** Dense(8, activation**=**'relu')(input1)
input2 **=** Input(shape**=**(32,))
x2 **=** Dense(8, activation**=**'relu')(input2)
added **=** Add()([x1, x2])
out **=** Dense(4)(added)
model **=** Model(inputs**=**[input1, input2], outputs**=**out)
这绝不是对 Keras functional API 的全面指导。如果你想了解更多,请参考文件。
让我们实现一个 ResNet
接下来,为了进行比较,我们将实现一个 ResNet 和它的普通(没有跳过连接)对应物。
我们将在这里构建的 ResNet 具有以下结构:
- 形状输入(32,32,3)
- 1 个 Conv2D 层,带 64 个滤波器
- 具有 64、128、256 和 512 个滤波器的 2、5、5、2 个残差块
- 平均池 2D 层,池大小= 4
- 展平图层
- 具有 10 个输出节点的密集层
它总共有 30 个 conv+致密层。所有内核大小都是 3x3。我们在 conv 层之后使用 ReLU 激活和批处理规范化。
除了跳过连接外,普通版本是相同的。
我们首先创建一个辅助函数,它将一个张量作为输入,并向其添加 relu 和批量归一化:
**def** relu_bn(inputs: Tensor) **->** Tensor:
relu **=** ReLU()(inputs)
bn **=** BatchNormalization()(relu)
**return** bn
然后我们创建一个函数来构造一个残差块。它接受一个张量x
作为输入,并通过 2 个 conv 层;让我们把这两个 conv 层的输出称为y
。然后把输入的x
加到y
,加上 relu 和批量归一化,然后返回结果张量。当参数为downsample == True
时,第一个 conv 层使用strides=2
将输出大小减半,我们使用输入为x
的kernel_size=1
的 conv 层,使其形状与y
相同。Add
层要求输入张量具有相同的形状。
**def** residual_block(x: Tensor, downsample: bool, filters: int, kernel_size: int **=** 3) **->** Tensor:
y **=** Conv2D(kernel_size**=**kernel_size,
strides**=** (1 **if** **not** downsample **else** 2),
filters**=**filters,
padding**=**"same")(x)
y **=** relu_bn(y)
y **=** Conv2D(kernel_size**=**kernel_size,
strides**=**1,
filters**=**filters,
padding**=**"same")(y)
**if** downsample:
x **=** Conv2D(kernel_size**=**1,
strides**=**2,
filters**=**filters,
padding**=**"same")(x)
out **=** Add()([x, y])
out **=** relu_bn(out)
**return** out
create_res_net()
函数把所有东西放在一起。以下是这方面的完整代码:
简单网络以类似的方式构建,但是它没有跳跃连接,我们也不使用residual_block()
助手函数;一切都在create_plain_net()
里面完成。
平原网络的代码:
在 CIFAR-10 上进行培训并查看结果
CIFAR-10 是一个包含 10 个类别的 32x32 rgb 图像的数据集。它包含 50k 训练图像和 10k 测试图像。
以下是每个班级 10 张随机图片的样本:
我们将在这个数据集上训练 ResNet 和 plain net 20 个时期,然后比较结果。
在配有 1 个 NVIDIA Tesla K80 的机器上,每个 ResNet 和 PlainNet 的训练时间约为 55 分钟。ResNet 和 PlainNet 在训练时间上没有显著差异。
我们得到的结果如下所示。
因此,通过在该数据集上使用 ResNet,我们在验证准确性方面获得了 1.59% 的提升。在更深的网络中,差异应该更大。请随意尝试,看看你得到的结果。
参考
- 用于图像识别的深度残差学习
- 残差神经网络—维基百科
- 功能 API 指南— Keras 文档
- 模型(功能 API) — Keras 文档
- 合并层— Keras 文档
- CIFAR-10 和 CIFAR-100 数据集
我希望这些信息对你有用,感谢你的阅读!
这篇文章也贴在我自己的网站这里。随便看看吧!