TowardsDataScience 博客中文翻译 2020(九百五十五)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

利用您的数据科学项目(第 1 部分)

原文:https://towardsdatascience.com/utilizing-your-data-science-project-32ef3f3230f4?source=collection_archive---------20-----------------------

将 Lending Club 机器学习模型投入生产

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我的 LendingClub 仪表板应用程序的图形用户界面

在过去几年中,数据科学行业最强劲的趋势之一是越来越重视在生产环境中部署机器学习模型。雇主期望的不仅仅是特征工程和建模。你执行至少一些基本软件工程任务的能力可能决定求职过程的成败。

我目前正在找工作。特别是因为我来自一个非技术背景,我不想把自己限制在只能拟合、预测和评分模型,而不能走出沙盒。在大多数现实世界的应用程序中(没有双关语的意思),一个模型将是一个更大的产品的一部分。构建一个将模型投入生产的简单程序,向您介绍生产环境特有的想法。就我而言,我还想证明我可以学习一个新的库或用例,而我并没有明确的课程。

还有一个更明显的原因让你想把你的模型投入生产:你可以使用它,或者和其他人分享它!我住在一个不允许投资者使用 Lending Club 的州,我希望能够与能够使用它的朋友或家人分享我的模型,无论他们是否受过 Python 训练。

对于那些不熟悉的人来说,LendingClub 是一个点对点的借贷市场。实质上,借款人申请贷款,并由 LendingClub 分配一个利率。个人投资者可以选择贷款来资助或投资,以类似于众包活动的方式为贷款筹集资金。作为投资者,你的回报根据你选择的贷款(利率和违约率)而变化。因此,如果你能更好地预测哪些借款人会还贷,你就可以期待更好的投资回报。

在我的一个个人项目中,我建立了一个模型,可靠地选择了一个超过市场平均水平的贷款组合。在我的模型被完全微调后(即“数据科学”部分完成),将模型投入生产有 5 个主要步骤。

保存/序列化您的模型

当你在 jupyter 笔记本上展示一个模型时,你的模型运行多长时间并不重要。然而,在生产环境中,速度通常很重要。即使在我们不断收到新数据的情况下,模型也很少需要立即重新训练。根据用例的不同,模型的训练可能只发生一次,在每天的某个时间,由外部事件触发,或者完全不同的事情。就我们的目的而言,在开发应用程序时,训练我们的模型一次就足够了。

为了以功能性的方式利用我们的模型,我们需要保存结果以供将来使用。这也称为序列化。在 python 中实现这一点的一个简单方法是使用 pickle 库

从库文档中:

[pickle](https://docs.python.org/3/library/pickle.html#module-pickle)模块实现了二进制协议,用于序列化和反序列化 Python 对象结构。*【pickle】是将 Python 对象层次转换成字节流的过程,【unpickle】*是逆操作,将字节流(来自二进制文件类字节对象)转换回对象层次。

我们需要知道的是,这个库为我们提供了一个简单的方法来保存一个合适的模型,这样我们就可以在应用程序启动时加载它,而不是每次我们想要运行程序时都必须适应一个可能很耗时的模型。

我设置了两个 python 程序。第一个包含所有的代码来训练我的应用程序将要使用的模型。程序做的最后一件事是保存这个模型以备将来使用。

rf.fit(X,y)
import pickle
pickle.dump(rf, open('rf.sav', 'wb'))

然后,在我们面向用户的程序中,当我们想要重新加载模型时:

rf = pickle.load(open(‘rf.sav’, ‘rb’))

这将在 Python 中加载我们的对象的实例,并允许我们像刚刚创建它一样使用它。

加载实时数据—使用 Lending Club API

这一步会有很大的不同,取决于你正在处理的具体问题。它可能涉及抓取网页、加载放入数据库的数据,或者为查看您的网页的客户加载客户信息。在这种情况下,我们希望利用我们的模型让我们知道哪些当前可资助的贷款可能给我们带来最高的投资回报。为了做到这一点,我们需要一个实时的数据源。

幸运的是,Lending Club 提供了一个易于使用的 API,我们可以使用请求库与之通信。Requests 允许您非常容易地发出 HTTP 请求,允许您与 web 页面和 API 进行交互。你可以在这里找到文档

前两行代码为我们的请求指定了变量和参数。我们的变量‘API key’是在用户登录(并输入他们的 API 密钥)时定义的,而‘Content-Type’规定了 API 返回的数据格式。Lending Club 列出了一天中四个不同时间的新贷款。参数“showAll”告诉 API 是返回所有可资助的贷款,还是只返回最后一个列表中的贷款。

header = {‘Authorization’: apikey, ‘Content-Type’: ‘application/json’}params = {‘showAll’: ‘True’}

以下代码从 Lending Club API 发出请求:

url = ‘https://api.lendingclub.com/api/investor/v1/loans/listing’resp = requests.get(url, headers=header, params=params)

最后,我们从响应中提取贷款信息,并将其格式化为 pandas 数据框。

loans = resp.json()[‘loans’]loans = pd.DataFrame.from_dict(loans)

我们现在有来自 LendingClub 的熟悉格式的实时数据。

处理实时数据

我不会在这里做得太深入,但是值得注意的是,如果您关心准确的预测,那么您对测试和训练数据执行的任何预处理步骤也必须对实时数据执行。这意味着离群值的去除、转换等。

这对于我们在数据清理和特征工程过程中所做的一些决定有影响。例如,在生产中以相同的方式实现对数转换比 box-cox 转换要简单得多。

使用模型预测还款概率

从程序上来说,这与预测测试数据集的概率没有太大区别。然而,这将是你第一次为动态数据释放模型,你不知道最终结果。你在预测未来!这是你通过几个月的教程或课堂作业努力争取的时刻,所以拥抱它吧!

用用户界面实现模型

这是另一个部分,将根据用例的不同而显著变化。有些模型,比如推荐系统,会影响应用程序的行为。我的情况简单一点。我只是显示预测概率以及实时贷款信息。

我的第一个程序只是一个脚本,它将带有模型结果的数据框打印到终端。这确实迫使我思考上面的许多教训,我认为这是一个完全富有成效和有价值的练习,如果就此而言的话。然而,我也想构建一个图形用户界面(大多数人认为是程序或应用程序)。我觉得这将大大提高我与他人分享我的程序的能力,我也觉得我不能创建一个应用程序是我应该补救的事情。

我选择 PyQt5 作为我的开发库。这个过程相当广泛,我在本文的第二部分中写了更多。有几个在线演练,以及文档,使之有可能拿起。Qt designer 也非常有用,它可以让你直观地设计一个窗口,并为你生成代码。

结论

作为申请人,将你的模型产品化,说明你比只知道在 jupyter 笔记本上评价模型的人更全能。你实际上已经考虑了你的模型在现实世界中的用途,并且潜在地权衡了你在更理论化的项目中不会有的权衡。创建一个图形用户界面显示了学习一个新的库和超越典型数据科学工具箱的分支的能力。

根据您的具体项目,将您的模型放入最终产品中可能会有一些实实在在的好处。我非常期待进一步开发我的应用程序和模型,这样我就可以与朋友和家人分享了。如果您目前正在寻找一份数据科学方面的工作,或者碰巧有一个有趣的副业项目,利用它将是有益的,您应该考虑尝试做同样的事情!

如果你对我如何为这个程序设置图形用户界面(GUI)的基础知识感兴趣,请在这里查看第二部分

疫苗学 3.0 —数据科学的作用

原文:https://towardsdatascience.com/vaccinology-3-0-the-role-of-data-science-6148f4742acb?source=collection_archive---------64-----------------------

回顾

当全世界屏息等待新冠肺炎疫苗的时候,这篇文章探索了数据科学对疫苗学的未来意味着什么?

T 何本文的目的是在数据科学技术的范围内揭示现代疫苗学的发展。它试图回答一些问题,这些问题引起了越来越多的关注,因为全世界都在看着科学家和制药公司在创纪录的时间内竞相开发武汉疫情病毒的疫苗。例如:

  • 开发现代疫苗的挑战是什么?
  • 在现代人工智能和数据科学工具的保护下,疫苗学 3.0 是如何发展的?

这是一篇综述文章,重点关注数据科学,但目标是涵盖疫苗学和相关概念的要点。因此,在尝试实现回顾数据科学在现代疫苗学中的机遇/成就的目标之前,有必要粗略浏览一下该领域中几个关键术语的定义。

定义

疫苗学是与疫苗开发有关的医学分支。其基本思想是将无害的实体引入体内,引发机体针对致病病原体的免疫反应,所述致病病原体持续存在于体内,从而防止未来的感染。根据引入体内的实体不同,疫苗有三种。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

全病原体疫苗(传统)引入死亡或减弱的病原体以引发终身免疫。亚单位疫苗仅由病原体成分(或抗原)和佐剂组成。佐剂是增强疫苗所需免疫反应的物质。必须提到的是,佐剂的安全性还存在争议。核酸疫苗基于引入编码抗原的遗传物质的方法,针对该抗原需要免疫。粗略地说,疫苗学 1.0 和 2.0 这两个术语可以分别归因于前两种疫苗的开发。

**疫苗学 3.0反向疫苗学 哪个更近涉及到病原体基因组信息使能的抗原发现过程。从解题的角度来看,疫苗学 3.0 类似于来自物理学的第一原理。它首先获得候选疫苗的流行病学信息,并对宿主-病原体相互作用进行建模,从而有效地减少候选疫苗的庞大列表。

组学 数据的爆炸式增长使这成为可能;组学是用来指来自(花式!)学科即。转录组学、蛋白质组学、代谢组学、细胞组学、免疫组学、分泌组学、表面组学或互作组学。由于这种大数据的支持,与之前自下而上和假设驱动的方法相比,逆向疫苗学本质上成为了一种自上而下的方法。因此,组学术语的最新补充是疫苗组学**,其定义为研究疫苗诱导的免疫反应。**

理解了这些关键术语,尽管是高层次的,并了解了疫苗开发的一些背景,我们可以深入研究数据科学的方法学,这种方法学使疫苗学 3.0 成为可能。为了有效地做到这一点,让我们列出疫苗开发中的关键挑战;这是逆向疫苗学的新范例,旨在有效解决这一问题。针对每一项挑战,强调如何利用数据科学和大数据技术。

疫苗开发的关键挑战

疫苗的开发和交付存在大量问题。例如,其中一个社会问题是反 vax 运动的增加。世界人口老龄化是另一个问题。梅奥诊所的科学家在的一篇文章中列出了当前的四大挑战,这里正在讨论。

1.对免疫学的不完全理解

人体及其免疫系统极其复杂,包含大量组件。尽管近几十年来取得了相当大的进展,但目前对这一系统的了解还远远不够。科学家不可能准确预测特定疫苗接种和相关感染的免疫系统行为。

这就是疫苗组学和系统生物学基于数据的计算方法为填补知识空白提供的机会。值得一提的是美国著名科学家、ACM 图灵奖获得者唐纳德·克纳特(Donald Knuth),他激励了多代计算机科学家。当他(承蒙:计算机扫盲书店,公司)时,Knuth 对计算生物学的范围非常乐观:

“我无法像对生物学一样对计算机科学充满信心。生物学很容易就有 500 年的令人兴奋的问题要解决,它处于那个水平。”唐纳德·克纳特。

这是他 1993 年采访的节选,事情朝着他预期的方向呈指数级发展。计算硬件、云技术以及最重要的深度学习打开了这一领域创新的闸门。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Unsplash科学高清拍摄的照片

**免疫系统中的一个主要潜在活动是由相对自主和特化的细胞执行的,这些细胞被称为 **T 细胞;它们通过它们的表面受体相互激活进行交流。最近,测量(全体)细胞状态、功能及其产物以及它们的基因编码的技术已经变得可行。这实质上产生了大量数据,可用于开发更全面的免疫系统模型——这在传统上是不可能的。因此,它催生了系统免疫学领域— 数据科学家的另一个游乐场。它还使科学家能够开发更好的佐剂,并提高免疫反应的持久性。

2.人类群体的可变性

人类群体的免疫反应因遗传倾向而异。这使得开发对所有人群都有效的单一疫苗变得困难。解决这个问题的方法是使用全基因组关联研究 (GWAS),其目的是识别特定感兴趣特征下的常见遗传变异。

对于门外汉,让我稍微深入一下基因组研究。生物是由细胞组成的。我们的每个细胞都由一个细胞核组成。细胞核由组织成染色体的 DNA 组成。DNA 有四个碱基,分别用 T、A、C、g 表示 DNA 测序 是确定一个 DNA 分子中这些碱基的物理顺序。在基因组数据科学中,基本上,从受试者收集样本,并进行 DNA 测序以定义受试者的基因组。你的基因组决定了你的身体如何工作。人类基因组的总长度超过 30 亿个碱基对。基因组由小的功能片段组成,这些功能片段可能与特定的过程和特征有关。基因组学涉及基因组的结构、功能、进化和绘图。

人类基因组计划是世界上最大的合作生物学项目,始于 80 年代,目标是识别和绘制人类基因组的所有基因,并于 2003 年实现。

回到 GWAS,它的主要目标是将特定的基因变异与特定的疾病联系起来。它包括扫描许多不同人的基因组,寻找可以用来预测疾病存在的遗传标记。最近的一个例子是,GWAS 针对新冠肺炎的一项研究报告显示,O 型血患者的行为差异很大,与 T2 a 型血患者的高风险相比,O 型血患者表现出了保护作用

从数据科学的角度来看,除了在实现更快更便宜的测序技术方面的巨大贡献外,许多机器学习方法也使受益于 GWAS。这篇文章很好地总结了许多 ML 对 GWAS 的贡献,例如,不同的方法如何处理“维度的诅咒”——数据的特征比样本多——这是 GWAS 的一个常见问题。

3。病原体可变性

伦敦大学国王学院的研究人员刚刚报道了美国、英国和瑞典的六种新冠肺炎病毒,患者人数只有几千人。他们使用了患者症状进展的时间序列数据,并对数据进行聚类,以确定归因于病毒可能变异的 6 种不同模式。属于这两类的病人需要重症监护。

病原体的可变性从两个方面对疫苗开发提出了挑战。首先,如果毒株的数量很高,那么制造对所有毒株都有效的疫苗是困难的,其次,病原体可能不断变异,需要不断地重新配制疫苗。

结合疫苗组学的基因组测序再次成为应对这一挑战的重要方法。

流感病毒疫苗开发的具体案例凸显了数据科学应对这一挑战的重要性。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

疾控中心Unsplash 拍摄的照片

流感疫苗的数据科学

流感有两种不同的表面蛋白,H 和 N,它利用这两种蛋白进入宿主细胞。这些蛋白质有多种变体,H1N1 和 H3N2 是两种最常见的亚型。1968 年至 1969 年,H3N2 杀死了世界上超过 100 万人。1918 年的 H1N1 导致了西班牙流感。****

由于上述病原体的可变性,流感疫苗每年都要重新配制。根据病毒以前和当前变种的数据(基因组),疫苗学家使用机器学习算法对病毒的未来行为进行建模和预测。实现这一点的方法之一是构建一个系统发育(进化)树。系统进化树代表了不同菌株在遗传学方面的接近程度。最大似然法系统发育分析( PAML )是一个帮助构建和分析树的软件包。为此,非负最小二乘回归是可以采用的另一种技术。同样,贝叶斯分析在这个研究中也是相当流行的。

4.疫苗安全

考虑到我们对免疫系统和疫苗与我们身体相互作用方式的理解存在差距,确保安全和避免新发明疫苗的任何副作用至关重要。在这个新闻和假新闻像病毒一样传播并带来许多不良后果的社交网络时代(例如,在最近疫苗管理安全失误后,最近世界上爆发了麻疹),疫苗安全极其重要。另一个例子,CDC 在这个链接提供了一个关于疫苗的九个安全问题的列表。最近一次是在 2013 年召回一批 HPV 疫苗。

登革热疫苗是一种疫苗,当给没有登革热病毒感染史的健康个体接种时,由于一种称为抗体依赖性增强** (ADE)的现象,实际上可能会增加严重登革热的风险。ADE 导致病毒的传染性增加,并已在登革热病毒、HIV、黄热病毒、寨卡病毒和冠状病毒中观察到。**

状语 是一个新的研究领域,旨在识别、表征和预测对疫苗的不良免疫反应。这些研究将我们带到了个性化疫苗时代的门口。它涉及复杂的生物统计学方法,以确定模式和因果网络的大量非常高的维度数据。

迈向疫苗学 3.0 的最新数据科学技术

现在让我们来讨论竞争对手互联网公司最近将人工智能和人工智能用于疫苗学的两个突出应用。

Alphafold(谷歌 DeepMind)

生物学的核心挑战之一是蛋白质折叠。蛋白质折叠问题是蛋白质的氨基酸序列如何决定其三维结构的问题。换句话说,蛋白质的结构可以从它的氨基酸序列预测出来吗?这很重要,因为通过折叠获得特定的三维结构表征了蛋白质的生物功能。据估计,人体含有 80,000 到 400,000 种蛋白质。

例如,人类肺细胞含有与新冠肺炎互补的特殊蛋白质,这种蛋白质激活病毒引发感染。因此,当设计针对新冠肺炎病毒的疫苗或药物时,解决蛋白质折叠问题至关重要。

Alphafold 是 DeepMind 开源的基于深度学习的蛋白质结构预测工具。本质上,它使用蛋白质数据库和蛋白质序列数据集(通过一段时间的 DNA 测序建立)来寻找与目标序列相似的序列。通过关联序列和靶序列之间氨基酸残基位置的变化,有可能预测残基之间的接触,然后用于预测蛋白质结构。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

亚历山大·波波夫Unsplash 上拍照

在引擎盖下,Alphafold 使用卷积神经网络来预测氨基酸残基之间的距离和扭转角度。这些值用于确定蛋白质的统计潜力。用一种非常简单的方式,把这想象成蛋白质系统的能量,蛋白质会以这种能量最小化的方式折叠自己。使用梯度下降,这种蛋白质潜力被最小化,以获得更精确的扭转角和距离。

DeepMind 已经表明,Alphafold 比最先进的方法更准确,它可能有利于蛋白质科学的所有领域。更重要的是,这可能会加快寻找对抗疫情的疫苗和药物的步伐。

线性设计(百度研究)

在讨论百度的 LinearDesign 之前,先简单说一下 Moderna 是有意义的。Moderna 是一家生物技术公司,致力于药物和疫苗开发,其技术完全基于 mRNA。对于外行人来说,RNA 是一种核酸,像 DNA 一样,但是是单链的。RNA 的关键过程之一是蛋白质合成。信使 RNA (mRNA)携带从 DNA 复制的遗传信息到细胞核外的蛋白质合成位点。然后,tRNA 和 rRNA 执行后续活动,本质上是作为氨基酸(蛋白质的构建模块)必须如何相互连接以形成蛋白质的指导者。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由国家癌症研究所Unsplash 上拍摄

像 Moderna 这样试图基于 mRNA 制造疫苗的公司,基本上是试图开发一种模拟病毒实际 mRNA 的合成 mRNA。病原体利用病毒 mRNA 在宿主体内构建病毒蛋白。因此,基于合成 mRNA 的疫苗的目标是欺骗免疫系统产生抗体,然后抗体将对抗真正的病毒。

这是一种开发病毒的新模式,目前仍处于激烈的研究中。Moderna 还试图利用这项技术制造冠状病毒疫苗,如果成功的话,这可能是第一个。

百度开发的 LinearDesign 是一款针对疫苗开发设计优化 mRNA 序列的工具。简而言之,这就是他们的工作要解决的问题——可能导致编码相同蛋白质序列的可能 mRNA 的数量呈指数增长,这使得寻找最佳 mRNA 设计变得困难。他们将这个设计问题简化为形式语言理论和计算语言学中的经典问题,然后使用近似算法来确定一个快速解决方案,与精确搜索确定的解决方案相比,该解决方案具有较高的准确性。

百度的 LinearDesign 受他们之前的技术 LinearFold 和类似工具的启发,对于开发新冠肺炎疫苗至关重要,一般来说,也是疫苗学 3.0 的基础。

特别是,基于 mRNA 的疫苗开发技术可能会开创一个疫苗开发和管理的新时代,这与 PlanetLabs 在太空技术中通过使用敏捷方法简化卫星的进化和开发非常相似。为此,数据科学和人工智能技术将发挥关键作用。

当前的疫情正在成为创新疫苗开发技术发展的催化剂,而数据科学是关键的推动者。

在 Linkedin 上找到我@https://www.linkedin.com/in/deepak-karunakaran

由于随机初始化和选择偏差,验证集过度拟合

原文:https://towardsdatascience.com/validation-set-over-fitting-because-of-random-initialization-selection-bias-cbb30d50095d?source=collection_archive---------23-----------------------

“提前停止”真的能保证 AI 模型泛化吗?

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

hawk 的标志:AI,我在这里担任首席数据科学家,感谢同事们的不断支持。

深度学习模型被称为通用函数逼近器。他们的优势来自于他们对给定的输入和输出之间的关系进行建模的强大能力。然而,这也是他们对问题提出一般化解决方案的主要弱点,也是他们如此倾向于过度适应(记忆)训练集而不使用新数据的原因。目前确保深度学习模型泛化的方法是简单地使用验证集来决定为训练模型应该进行多少次迭代(时期);或者换句话说,提前停止。然后,数据科学家将在盲测试集上测试训练的模型,以确保没有训练超参数也是过度拟合的。到目前为止,这种方法在处理这样的问题时效果很好,在这些问题中,你可以有这样的独立数据集,每个数据集都有相似的分布。然而,对于金融或医疗保健(这两个领域我都有很深的经验)中的一些问题来说,情况并非如此,对于强化学习问题来说当然也是如此,在强化学习问题中,新数据既依赖于环境,也依赖于行动。

早期停止经常失败的关键场景示例!

在医疗保健中,部署的模型对每个看不见的患者进行概括是极其重要的,这些患者既不会出现在训练集中,也不会出现在测试集中。由于这个原因,通常的做法是用留一个受试者出来交叉验证来验证模型,其中每个留一个受试者将是不同的,并试图基于每个折叠的统计来决定早期停止时期;这在人口众多的情况下是可以接受的。在金融领域,情况甚至更糟,因为人们需要对未来数据进行归纳;同时,股票市场数据的分布总是变化的,几乎不可能有如此同质的训练、验证和测试集。一些定量分析师训练人工智能模型进行交易,尽管他们在实验期间的回溯测试中表现很好,但他们在真实数据中往往表现不佳。最后,大多数强化学习方法都有再现性问题(特别是当使用不同的随机种子时),而像增强随机搜索这样的无梯度方法在这些方法中表现得更好。最后,相同模型的集合(甚至)通常会产生更好的结果。

良好的验证性能并不是概括的指标!

深度学习模型(迭代机器学习模型)的持续学习也是一场噩梦,需要同样的验证。事实上,如果使用相同数量的历元用相同大小的新数据集从头开始重新训练,人们永远无法确保深度学习模型在泛化方面收敛到相同或更好的解决方案。简直是一团糟!经过多年训练模型的经验,我已经确定了这种情况的主要原因是验证集过度拟合。由于任何机器学习模型都是随机初始化的,它们可以收敛到局部最小值,这在使用验证集时表现很好,如果你在这一点上提前停止训练,模型的泛化能力将低于最优解。对于我作为例子给出的问题,没有办法通过检查验证集来确保这种情况不会发生。我发现了一种仅通过使用训练集来检测过度拟合的好方法,即跨许多小批量检查模型性能。由于该模型在过拟合期间会失去其泛化能力,当试图学习其他模型时,它会在某些小批量中表现更差,因此在训练期间损失的变化会在它们之间波动。

“基于群体”的持续再平衡投资组合选择!

最近,我在研究投资组合选择(优化)问题时遇到了这个问题,我终于发现了一个解决方案,它可能适用于其他领域。我发明了一种方法,我将其命名为“基于群体的常数再平衡投资组合选择”。对于这种方法,我使用 PyTorch 中的自动签名功能在 GPU 中同时优化 8192 个投资组合。然后,我使用前 50%投资组合的平均权重来检查验证集的性能,而不是从正在训练的投资组合权重中选择一个候选。(当它们都用于取平均值时,该方法收敛到类似的结果,并且使用前 50%只是加速了训练过程。)总之,结果证明我能够获得平滑的训练和验证曲线,我能够使用该曲线来确定没有随机初始化偏差的早期停止时期。这种方法的另一个有趣的可能用途是同时进化的对抗例子,用于攻击深度学习模型。与现有的进化优化方法的主要区别在于,在所提出的方法中,每个候选项被独立地优化。关于该方法的进一步细节如下:

关于提议的“基于群体”的常数再平衡投资组合选择方法的陈述。

文献中现有的投资组合选择方法已经过时了!

以前的投资组合选择方法只解决了最优购买和持有投资组合的选择,但没有帮助选择一个不断再平衡的投资组合。同时,一个最小波动率常数再平衡投资组合,当简单持有时没有正回报,也可以由于均值回复而产生利润。因此,我更倾向于在交易成本(1%)之下,为给定的交易政策(如 UCRP)和风险调整后的回报选择一个最佳投资组合。此外,用于决定何时重新平衡回所选恒定投资组合的散度阈值也与投资组合权重一起被优化。据我所知,这也是第一个投资组合选择方法,通过模拟策略来优化投资组合以及定义的交易策略的参数。奖励函数作为有效前沿优化的替代。

80%胜率和 20 倍盈亏比的战略源代码

总之,这种方法使我能够为给定的交易策略(在这种情况下是常数再平衡投资组合)找到通用的投资组合权重和参数,并定义风险敏感的回报函数。事实上,我已经能够优化这样一个交易策略,在 QuantConnect 进行的样本外回溯测试中,它能够实现 80%的胜率和 20 倍的盈亏比。为了展示这种方法的优势,我在下面的链接中分享了这种策略的源代码;这样任何人都可以自己复制结果。请注意,即使在训练集上也很难找到这样的策略。最后,我期待其他研究人员为我在本文中定义的问题开发更多的解决方案;我希望它能引发更多高质量的研究来解决人工智能中的这个严重问题。

[## 基于群体选择的交易策略源代码

在基于群体的常数再平衡投资组合选择的策略参数的优化过程中,不利用该回测期。

www.quantconnect.com](https://www.quantconnect.com/terminal/processCache?request=embedded_backtest_dfc0fa7d17e0029e9a7dee3483fe8b7c.html)

感谢阅读!作者(我)的简短自动传记:

我是一名前院士(T-Labs,微软研究院)和企业家(OTA 专家,客厅),他改革了工作中的帕金森病治疗(ConnectedLife ),同时入侵了国内的股票市场。我以前在著名的研究机构工作过,包括微软剑桥研究院计算机媒介生活实验室的社会数字系统(人类体验和设计)组和德国电信创新实验室(T-Labs)的质量和可用性组。我领导了几个关于深度学习、机器学习、模式识别、数据挖掘、人机交互、信息检索、人工智能、计算机视觉和计算机图形学的研究项目;并在许多会议和期刊上合作发表了 35 篇论文。我目前在 hawk:AI 从事金融科技领域的工作,担任首席数据科学家。

有价值的数据分析与熊猫价值计数

原文:https://towardsdatascience.com/valuable-data-analysis-with-pandas-value-counts-d87bbdf42f79?source=collection_archive---------10-----------------------

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由莎伦·麦卡琴Unsplash 拍摄

使用这个简单的工具,你可以做比你想象的更多的数据分析

流行的 python 数据科学库 Pandas 中的**value_counts()**函数是一种快速计算单个列中唯一值的方法,也称为一系列数据。

这个函数对于在 Pandas 数据帧中包含的特定数据列上快速执行一些基本的数据分析非常有用。关于熊猫数据帧的介绍,请看上周的帖子,可以在这里找到。

在下面的文章中,我将向你展示一些使用这个工具进行数据分析的技巧。这篇文章将向你展示如何在你的代码中添加一些东西,你就可以使用这个函数进行大量的分析。

数据

在本文展示的例子中,我将使用来自 Kaggle 网站的数据集。它是为机器学习分类任务而设计的,包含有关医疗预约的信息和一个目标变量,该变量表示患者是否赴约。

可以在这里 下载

在下面的代码中,我已经导入了我将在整篇文章中使用的数据和库。

import pandas as pdimport matplotlib.pyplot as plt
%matplotlib inlinedata = pd.read_csv('KaggleV2-May-2016.csv')
data.head()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

来自 Kaggle.com 的医疗预约失约数据集的前几行

基本计数

可以通过以下方式使用value_counts()函数来获得数据集中某一列的唯一值的计数。下面的代码给出了性别列中每个值的计数。

data['Gender'].value_counts()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

要按升序或降序对值进行排序,我们可以使用排序参数。在下面的代码中,我添加了**sort=True**,以降序显示年龄列中的计数。

data['Age'].value_counts(sort=True)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

与 groupby()结合

value_counts 函数可以与其他 Panadas 函数结合使用,以获得更丰富的分析技术。一个例子是与**groupby()**函数结合。在下面的例子中,我正在计算性别列中的值,并应用groupby()来进一步了解每组中未出现的人数。

data['No-show'].groupby(data['Gender']).value_counts(sort=True)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使标准化

在上面的例子中,显示绝对值不容易使我们理解两组之间的差异。更好的解决方案是显示每个组中唯一值的相对频率。

我们可以将 normalize 参数添加到 value_counts()中,以这种方式显示值。

data['No-show'].groupby(data['Gender']).value_counts(normalize=True)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

扔掉

对于有大量唯一值的列,value_counts()函数的输出并不总是特别有用。一个很好的例子是年龄列,我们在这篇文章的前面显示了它的值。

幸运的是,value_counts()有一个bin参数。此参数允许我们以整数形式指定箱(或我们想要将数据分成的组)的数量。在下面的例子中,我添加了**bins=5**来将年龄分为 5 组。我们现在有了每个箱中的值的计数。

data['Age'].value_counts(bins=5)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

同样,显示绝对数字并不是特别有用,所以让我们也添加normalize=True参数。现在我们有了一个有用的分析。

data['Age'].value_counts(bins=5, normalize=True)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

与 nlargest()结合使用

在我们的数据集中还有其他列有大量的唯一值,宁滨仍然不能为我们提供有用的分析。一个很好的例子就是邻域列。

如果我们简单地对此运行 value_counts(),我们会得到一个不是特别有见地的输出。

data['Neighbourhood'].value_counts(sort=True)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

展示这一点的更好方法可能是查看排名前 10 位的社区。我们可以通过结合另一个名为**nlargest()**的熊猫函数来做到这一点,如下所示。

data['Neighbourhood'].value_counts(sort=True).nlargest(10)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们还可以使用**nsmallest()**来显示排名最末的 10 个街区,这可能也很有用。

data['Neighbourhood'].value_counts(sort=True).nsmallest(10)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

测绘

另一个方便的组合是 Pandas 绘图功能和 value_counts()。将我们从 value_counts()获得的分析显示为可视化的能力可以使查看趋势和模式变得容易得多。

我们可以显示以上所有的例子,以及 Pandas 库中可用的大多数绘图类型。可用选项的完整列表可在这里找到。

让我们看几个例子。

我们可以使用条形图来查看排名前 10 的社区。

data['Neighbourhood'].value_counts(sort=True).nlargest(10).plot.bar()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以制作一个饼图来更好地显示性别栏。

data['Gender'].value_counts().plot.pie()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

value_counts()函数通常是我进行数据分析的第一个起点,因为它使我能够非常快速地绘制趋势图,并从数据集中的各个列中获得洞察力。本文已经给出了可以使用该函数的各种类型的分析的快速概述,但是该函数有更多的用途,超出了本文的范围。

感谢阅读!

我每月都会发一份简讯,如果你想加入,请点击此链接注册。期待成为您学习旅程的一部分!

价值函数逼近—控制方法

原文:https://towardsdatascience.com/value-function-approximation-control-methods-a7a18e46ba29?source=collection_archive---------46-----------------------

强化学习之旅

未知、无模型环境中的控制算法

欢迎回到我的强化学习专栏。今天,我们将继续建立在我的上一篇关于价值函数逼近的文章的基础上。在上一篇文章中,我们展示了价值函数逼近中使用的政策预测方法,这一次,我们将看看控制方法。

我在专栏的旧帖子中提到了这些帖子中使用的很多符号。此外,像往常一样,为我提供了对强化学习世界的惊人见解的资源将在帖子底部链接到。事不宜迟,让我们更深入地探讨价值函数逼近。

在这一点上,广义策略迭代对我们来说应该不是新概念。这里的变化将是使用近似策略评估。我们将从一些参数向量 w 开始,定义一些值函数。然后,我们将贪婪地进行一点探索,关于价值函数,给我们一个新的政策。然后,为了评估这个新策略,我们更新我们的价值函数的参数,不断重复这个过程,直到我们(希望)收敛到一个最优的价值函数。下图说明了这一过程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

你会注意到,像以前一样,我们不需要一直走到顶线,或者换句话说,浪费时间/经验样本去尝试精确地拟合我们的函数逼近器。在我们的政策稍作调整后,我们立即行动,掌握最新的数据。

上述算法的问题是可能找不到最佳值函数,因为在现实中,我们只是越来越接近近似值函数。

现在,我们的第一步将是近似动作值函数,而不是状态值函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

对于每一个状态和任何行为,用参数 w ,我们建立一个函数,预测我们期望从那个状态和行为中得到多少回报。

我们最小化近似动作值函数和真实动作值函数之间的均方误差。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用链式法则和随机梯度下降,我们找到一个局部最小值:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们考虑最简单的情况,使用线性动作值函数近似。我们构建一个特征向量来表示状态动作:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这些特征解释了整个状态-动作空间。我们通过构建特征的线性组合来做到这一点,但我们也可以使用更复杂的系统,如神经网络。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后,梯度下降更新收缩为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在我们来看一个例子。下面的例子是强化学习中最广泛使用的问题之一,也是一个有趣的问题。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的想法是这辆车陷入了困境。汽车没有足够的动力直接开上山坡到达目的地,所以它必须向上开一点,松手,允许自己向后摆动通过下坡和后面的山坡,获得一些动量,并不断重复这个过程,直到它有足够的动量到达顶部。我们想要实现这个目标,即在不知道世界动力学(如重力、摩擦力等)的情况下,无模型地计算出控制策略。

首先,我们来定义这个问题的状态空间。这里的状态空间是汽车的位置,以及速度,使它成为一个二维的状态空间。

下面的伪代码显示了一个用于逼近最优值函数的分段 Sarsa TD 控制方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在让我们的山地汽车代理运行 100 集之后,结果显示了不可否认的学习:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

感谢你再次加入我对强化学习知识的探索。从这里开始只会变得更好,因为下一次我们将着眼于深度 Q 学习,使用深度神经网络作为函数逼近器,建立在这篇文章和上一篇文章的概念之上。期待再次学习!

R 资源

强化学习:简介作者萨顿和巴尔托

YouTube 上大卫·西尔弗的 RL 课程

强化学习 Githubdennybritz

OpenAI 旗下健身房

价值函数逼近—预测算法

原文:https://towardsdatascience.com/value-function-approximation-prediction-algorithms-98722818501b?source=collection_archive---------22-----------------------

强化学习之旅

大型未知环境的学习方法

欢迎再次深入强化学习!这一次,我们将回顾价值函数近似,更具体地说,它背后的预测算法,理解它的用途,并围绕实现进行思考。在我的下一篇文章中,我将像往常一样,通过将预测控制与一个挑战结合起来,将价值函数近似法绑在一起。然而,这篇文章将主要包括价值函数逼近和我们目前所学的一切之间的联系。

正如我们逐渐意识到的,有可能遇到非常大的问题或状态空间;甚至是无限/连续的状态空间。有时,环境就像 gridworld 一样简单,就像国际象棋等游戏的状态空间一样定义明确,但情况并非总是如此。例如,在机器人领域,遥控直升机可能会遇到无限多的情况/状态,无法对每一种情况/状态的实际值进行分类(以前从未见过类似的情况)。

我在专栏的前几篇文章中提到了这篇文章中使用的很多符号。我会在文章底部贴上非常有用的资源,帮助我理解这些概念。我们来看看什么是价值函数逼近。

回顾过去,我们会注意到,我们一直在通过非常具体地表示价值函数,利用查找表来解决马尔可夫决策过程(MDP)。每个状态 s 都有一个条目 V(s) ,每个状态-动作对 s,a 都有一个条目 Q(s,a) 。实际上,我们能够查看我们的表,并通过最大化所有可能的行动来决定下一步做什么。

在大型 MDP 事件中使用这种策略会暴露出两个问题,第一个问题是最终我们会耗尽内存。在某些时候,将会有太多的状态和/或动作需要存储。第二个问题很简单,即使我们有足够的内存,单独估计每个值的过程也太慢了。价值函数逼近是解决这一问题的方法。

价值函数逼近试图通过创建使用较少参数的价值函数的紧凑表示来构建一些函数以估计真实的价值函数:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

一种常见的做法是使用深度学习——在这种情况下,神经网络的权重是权重向量*,它将用于估计整个状态/状态-动作空间的价值函数。这个权重向量将使用我们之前见过的方法更新,蒙特卡罗时间差学习。*

我们将从查看如何利用价值函数近似中的随机梯度下降来调整每个示例后的权重向量开始。目标是找到使近似值函数和真实值函数之间的均方误差最小化的参数向量 w 。梯度下降通过找到局部最小值来做到这一点:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为了更好地理解这个概念,让我们来理解什么是特征向量*。每个特征都告诉我们关于状态空间的任何事情。它允许我们严格定义我们用什么来代表环境/我们与环境的互动。通过将特征的线性组合编辑在一起,我们用加权和表示价值函数。从数学上讲,我们的特征向量和我们的权重向量的点积将是我们对价值函数的估计。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们的目标函数,我们希望优化的均方误差函数,在参数 w 中变成二次函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用以下更新规则,随机梯度下降收敛于全局最优:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们权重的变化来自于一小步,取决于步长参数,根据特定情况调整相关的功能。

现在,为了使这与强化学习相关,我们必须利用我们的学习方法。显然,最初并不知道真正的价值函数。因此,我们在一个目标中代入*。在蒙特卡罗学习中,目标是收益 Gₙ*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 TD(0)中,目标是 TD 目标:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

更新看起来像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作为即将到来的高峰,下图应该看起来很熟悉!我们将基于广义策略迭代* (GPI)的思想,利用价值函数逼近进行控制。*

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

类似于上面的状态值函数近似,我们可以用随机梯度下降来近似动作值函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在下一篇文章中,我们将能够将这些预测信息与必要的控制方法结合起来,以便能够使用价值函数近似法实际解决问题。感谢阅读!

资源

强化学习:萨顿和巴尔托 介绍

YouTube 上大卫·西尔弗的 RL 课程

强化学习 Github by dennybritz

Q-函数的值迭代

原文:https://towardsdatascience.com/value-iteration-for-q-function-ac9e508d85bd?source=collection_archive---------47-----------------------

深度强化学习讲解— 11

Q 函数的冻结湖码

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

的上一篇文章中,我们介绍了如何通过求解冰湖环境来实现计算状态值 V-function 的值迭代法。在本帖中,我们将回顾 Q 函数,并展示学习动作值以创建策略的值迭代方法。

正如我们将在本文后面看到的,Q 值在实践中更方便,对于代理来说,基于 Q 值做出行动决策比基于 V 值更简单。

本出版物西班牙文版

[## 4.数字电视节目

请访问第 4 页的自由介绍

medium.com](https://medium.com/aprendizaje-por-refuerzo/4-programaci%C3%B3n-din%C3%A1mica-924c5abf3bfc)

Q 函数的值迭代

值迭代法可用于学习 V 值或 Q 函数。也就是说,将状态值或动作值存储在一个表中。在这里,我们将介绍如何使用值迭代方法来计算 Q 值,而不是上一篇文章中介绍的 V 值。

基于 V-function 的先前值迭代方法实现,在动作值的情况下,只需要对先前的代码进行微小的修改。最明显的变化是我们的价值表。在前一个例子中,我们保存了状态的值,所以字典中的键只是一个状态。现在我们需要存储 Q 函数的值,它有两个参数:state 和 action,所以值表中的键现在是一个复合键。

关于更新,在关于贝尔曼方程的帖子中,我们表明我们行动状态的最优值可以定义为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接下来,我们将使用 Q 函数来求解冰湖环境。

Q 函数的值迭代在实践中的应用

这篇文章的全部代码可以在 GitHub 上找到,而可以通过这个链接作为一个谷歌笔记本来运行。

然后,我们将在用 Q-函数构成值迭代方法的代码中展示与 V-函数版本的主要区别。

保存我们的表和函数的中心数据,我们将在训练循环中使用,与前面的 V-function 示例相同。主要的变化是对values表的修改,这个字典现在将一个状态-动作对映射到这个动作的计算值。

在前一个例子中,我们保存了状态的值,所以字典中的键只是一个状态。现在我们需要存储 Q 函数的值,它有两个参数:state 和 action,所以值表中的键现在是一个复合键。这意味着另一个区别在于calc_action_value函数。我们只是不再需要它,因为我们的动作值存储在值表中。

最后,代码中最重要的变化是代理的value_iteration()方法。以前,它只是一个围绕calc_action_value()调用的包装器,完成贝尔曼近似的工作。现在,由于这个函数已经消失并被一个值表所取代,我们需要在value_iteration()方法中做这个近似。

Q 函数的冻结湖码

让我们看看代码。由于与之前的实现几乎相同,我将直接跳到主要区别,读者可以在 GitHub 代码中了解细节。先说主要功能value_iteration():

def value_iteration_for_Q(self):
    for state in range(self.env.observation_space.n):
        for action in range(self.env.action_space.n):
            action_value = 0.0
            target_counts = self.transits[(state, action)]
            total = sum(target_counts.values())
            for tgt_state, count in target_counts.items():
                key = (state, action, tgt_state)
                reward = self.rewards[key]
                best_action = self.select_action(tgt_state)
                val = reward + GAMMA * \
                      self.values[(tgt_state, best_action)]
                action_value += (count / total) * val
           self.values[(state, action)] = action_value

读者会注意到,该代码相当于前面实现中的calc_action_value代码。其思想是,对于给定的状态和动作,它需要使用我们通过函数play_n_random_steps收集的信息来计算动作值,该函数从环境中随机播放N步骤,用随机经验填充rewardtransits表。

然而,在前面的实现中,我们将 V 函数存储在值表中,所以我们只是从该表中取出它。我们不能再这样做了,所以我们必须调用select_action方法,它将为我们选择 Q 值最大的最佳动作,然后我们将这个 Q 值作为目标状态的值。

事实上,这个方法的实现是不同的,因为它不再调用calc_action_valu方法,而是我们只是迭代动作,并在我们的值表中查找它们的值。

def select_action(self, state):
    best_action, best_value = None, None
    for action in range(self.env.action_space.n):
        action_value = self.values[(state, action)]
        if best_value is None or best_value < action_value:
           best_value = action_value
           best_action = action
    return best_action

正如你所注意到的,学习循环的代码和前一篇文章中的一样。以及测试客户端并在 TensorBoard 中绘制结果的代码:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后,读者可以像以前一样,用 FrozenLake8x8 环境进行测试,或者测试其他超参数。

鸣谢:这篇文章中的代码灵感来自于马克西姆·拉潘的代码 ,他写了一本关于 主题的优秀实用书籍。

结论

在实践中,Q 值要方便得多,对于代理来说,基于 Q 值做出关于动作的决策要比基于 V 值简单得多。在 Q 值的情况下,为了基于状态选择动作,代理只需要使用当前状态计算所有可用动作的 Q 值,并选择具有最大 Q 值的动作。

为了使用状态的值来做同样的事情, **V 值,代理不仅需要知道值,还需要知道转移的概率。**在实践中,我们很少预先知道它们,所以代理需要估计每个动作和状态对的转移概率。

在 V-函数的值迭代方法中,这种对概率的依赖给代理增加了额外的负担。也就是说,了解这种方法很重要,因为它们是高级方法的重要组成部分。

下期见!

深度强化学习讲解系列

UPC 巴塞罗那理工 巴塞罗那超级计算中心

一个轻松的介绍性系列以一种实用的方式逐渐向读者介绍这项令人兴奋的技术,它是人工智能领域最新突破性进展的真正推动者。

[## 深度强化学习解释-乔迪托雷斯。人工智能

本系列的内容](https://torres.ai/deep-reinforcement-learning-explained-series/)

关于这个系列

我在五月份开始写这个系列,那是在巴塞罗那的禁闭期。老实说,由于封锁,在业余时间写这些帖子帮助我到了#寄宿家庭 。感谢您当年阅读这份刊物;这证明了我所做的努力。

免责声明 —这些帖子是在巴塞罗纳封锁期间写的,目的是分散个人注意力和传播科学知识,以防对某人有所帮助,但不是为了成为 DRL 地区的学术参考文献。如果读者需要更严谨的文档,本系列的最后一篇文章提供了大量的学术资源和书籍供读者参考。作者意识到这一系列的帖子可能包含一些错误,如果目的是一个学术文件,则需要对英文文本进行修订以改进它。但是,尽管作者想提高内容的数量和质量,他的职业承诺并没有留给他这样做的自由时间。然而,作者同意提炼所有那些读者可以尽快报告的错误。

V 函数的值迭代

原文:https://towardsdatascience.com/value-iteration-for-v-function-d7bcccc1ec24?source=collection_archive---------33-----------------------

深度强化学习讲解—10

v 函数在冰湖环境中的应用

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

之前的帖子中,我们提出了值迭代方法来计算基于价值的代理所需的 V 值和 Q 值。在本文中,我们将介绍如何通过求解冰湖环境来实现计算状态值的值迭代法。

本出版物的西班牙语版本

[## 4.数字电视节目

请访问第 4 页的自由介绍

medium.com](https://medium.com/aprendizaje-por-refuerzo/4-programaci%C3%B3n-din%C3%A1mica-924c5abf3bfc)

V 函数的值迭代在实践中的应用

这篇文章的完整代码可以在 GitHub 上找到,并且可以使用这个链接作为一个 Colab google 笔记本运行。

接下来,我们将详细介绍组成我们在上一篇文章中介绍的值迭代方法的代码。所以让我们来看看代码。开始时,我们导入使用的库并定义主要的常量:

import gym
import collections
from torch.utils.tensorboard import SummaryWriterENV_NAME="FrozenLake-v0"GAMMA = 0.9
TEST_EPISODES = 20
N =100
REWARD_GOAL = 0.8

代理的数据结构

保存代理信息的主要数据结构有:

  • rewards:组合键为“源状态”+“动作”+“目标状态”的字典。价值是从眼前的奖励中获得的。
  • transits:一个表作为字典,保存所经历的转变的计数器。关键是复合“state”+“action”,值是另一个字典,它将目标状态映射到我们看到它的次数。
  • values:将一个状态映射到这个状态的计算值(V 值)的字典。
  • state:代理的当前状态。

主数据结构是在代理的类构造函数中创建的。

训练算法

我们训练算法的整体逻辑很简单。在未达到预期奖励目标之前,我们将执行以下步骤:

  1. 从环境中随机选择 N 步来填充rewardtransits表格。
  2. 在这 N 个步骤之后,它对所有状态执行一个值迭代步骤,更新value表。
  3. 然后,我们播放几集完整的剧集,使用更新后的值表来检查改进。
  4. 如果这些测试的平均回报高于期望的界限,那么我们就停止训练。

在我们深入了解该代码的更多细节之前,先回顾一下代理的代码会有所帮助。

代理类

Agent类构造函数中,我们创建了一个将用于数据样本的环境,获得了我们的第一个观察值,并定义了奖励、转换和值的表:

class Agent: 
      def __init__(self):
          self.env = gym.make(ENV_NAME) 
          self.state = self.env.reset()
          self.rewards = collections.defaultdict(float)
          self.transits = collections.defaultdict(
                        collections.Counter)
          self.values = collections.defaultdict(float)

玩随机步骤

请记住,在上一篇文章中,我们提出了转换和奖励的估计将通过代理与环境的交互历史来获得。

这是通过方法play_n_random_steps完成的,它从环境中随机播放N步骤,用随机体验填充rewardtransits表格。

def play_n_random_steps(self, count):
    for _ in range(count):
        action = self.env.action_space.sample()
        new_state, reward, is_done, _ = self.env.step(action)
        self.rewards[(self.state, action, new_state)] = reward
        self.transits[(self.state, action)][new_state] += 1
        if is_done:
           self.state = self.env.reset()
        else:
           self.state = new_state

注意,我们不需要等到一集结束才开始学习;我们只是执行N步骤并记住它们的结果。这是之前一个帖子展示的值迭代和交叉熵方法的区别之一,需要全集学习。

行动的价值

下一个方法使用代理的transitsrewardvalue表计算 Q 函数,即来自一个状态的动作值。我们将把它用于两个目的:从状态中选择要执行的最佳动作,并根据值迭代算法计算状态的新值。

def calc_action_value(self, state, action):
    target_counts = self.transits[(state, action)]
    total = sum(target_counts.values())

    action_value = 0.0
    for tgt_state, count in target_counts.items():
        reward = self.rewards[(state, action, tgt_state)]
        val = reward + GAMMA * self.values[tgt_state]
        action_value += (count / total) * val
    return action_value

首先,从transits表中,我们提取方法作为参数接收的给定状态和动作的转换计数器。我们对所有的计数器求和,以获得我们从状态执行动作的总次数。然后,我们迭代我们的动作已经到达的每个目标状态,并使用贝尔曼方程文章中的公式计算它对总动作值的贡献:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

该值等于即时奖励加上目标状态的贴现值,并将该总和乘以该转换的概率(单个计数器除以之前计算的总值)。我们将每次迭代的结果添加到一个变量action_value,这个变量将被返回。

选择最佳操作

为了从给定的状态中选择最佳的动作,我们有方法select_action,它使用前面的calc_action_value方法来做出决定:

def select_action(self, state):
    best_action, best_value = None, None
    for action in range(self.env.action_space.n):
        action_value = self.calc_action_value(state, action)
        if best_value is None or best_value < action_value:
           best_value = action_value
           best_action = action
    return best_action

该方法的代码迭代环境中所有可能的动作,计算每个动作的值,并返回具有最大 Q 值的动作。

值迭代函数

这里我们有一个主要功能,正如我们在上一篇文章中所描述的,值迭代方法所做的只是在环境中的所有状态上循环:

def value_iteration(self):
    for state in range(self.env.observation_space.n):
        state_values = [
              self.calc_action_value(state, action)
              for action in range(self.env.action_space.n)
        ]
   self.values[state] = max(state_values)

对于每个状态,我们用该状态可用的动作的最大值来更新它的值。

训练循环和监控代码

在介绍了代理的类及其方法之后,我们回来描述主循环。首先,我们创建将用于测试的环境,创建代理类的实例,TensorBoard 的摘要编写器,以及我们将使用的一些变量:

test_env = gym.make(ENV)
agent = Agent()
writer = SummaryWriter()iter_no = 0
best_reward = 0.0

记住值迭代法形式上需要无限次迭代才能精确收敛以获得最优值函数。实际上,在上一篇文章中,我们展示了一旦值函数在训练循环的迭代中仅发生少量变化,我们就可以停止。

在这个例子中,为了保持代码简单和迭代次数少,我们决定在达到某个奖励阈值时停止。但是其余的代码都是一样的。

我们代码的整体逻辑是一个简单的循环,它将迭代直到代理达到预期的性能(如果这些测试集的平均回报高于REWARD_GOAL界限,那么我们停止训练):

while best_reward < REWARD_GOAL:
        agent.play_n_random_steps(N)
        agent.value_iteration()

        ...

循环体由前面介绍的步骤组成:

步骤 1:播放调用方法plays_n_random_steps.的 N 个随机步骤

第 2 步:调用value_iteration.方法,对所有状态进行值迭代扫描

第三步:然后我们播放几集完整的剧集,使用更新后的值表来检查改进。为此,代码使用agent.elect_action()来寻找在新的test_env环境中采取的最佳行动(我们不想弄乱用于收集随机数据的主环境的当前状态),以检查代理的改进:

iter_no += 1
reward_avg = 0.0
for _ in range(TEST_EPISODES):
    total_reward = 0.0
    state = test_env.reset()
    while True:
        action = Select_action(state)
        new_state, new_reward, is_done, _ = test_env.step(action)
        total_reward += new_reward
        if is_done: break
        state = new_state
    reward_test += total_reward
reward_test /= TEST_EPISODES

此外,代码将数据写入 TensorBoard,以便跟踪最佳平均奖励:

writer.add_scalar("reward", reward_test, iter_no)
if reward_test > best_reward:
    print("Best reward updated %.2f at iteration %d " % 
         (reward_test ,iter_no) )
    best_reward = reward_test

仅此而已!

运行程序

好,让我们运行我们的程序:

Best reward updated 0.40 in iteration 13 
Best reward updated 0.65 in iteration 20 
Best reward updated 0.80 in iteration 23 
Best reward updated 0.85 in iteration 28 
Best reward updated 0.90 in iteration 76

测试客户端

现在,如果我们尝试使用与交叉熵相同的客户端测试代码,我们可以看到我们构建的代理可以从一个不稳定的环境中学习:

new_test_env = gym.make(‘FrozenLake-v0’, is_slippery=True)
state = new_test_env.reset()
new_test_env.render()
is_done = False
iter_no = 0
while not is_done:
    action = Select_action(state)
    new_state, reward, is_done, _ = new_test_env.step(action)
    new_test_env.render()
    state = new_state
    iter_no +=1
print(“reward = “, reward)
print(“iterations =”, iter_no)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

. . .

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

请注意,提出的算法是随机的,对于不同的执行,它采用不同的迭代次数来获得解决方案。然而,它可以从光滑的环境中学习,而不是前面提到的交叉熵。我们可以使用 Tensorboard 绘制它们,它会显示如下图所示的图形:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们可以注意到,在所有情况下,最多需要几秒钟就可以找到一个在 80%的运行中解决环境问题的好策略。如果你还记得交叉熵方法,对于一个湿滑的环境,花了许多小时才达到只有 60%的成功率。

而且,我们可以把这个算法应用到更大版本的 FrozenLake 上,这个版本的名字是 FrozenLake8x8-v0。更大版本的 FrozenLake 可能需要更多的迭代来求解,根据 TensorBoard 图表,大多数时间它会等待第一个成功的情节(它需要至少有一个成功的情节来开始从有用值表学习),然后它会很快达到收敛。下图比较了在 FrozenLake-4x4 和 8x8 版本上训练期间的奖励动态:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

除了在ENV_NAME(“frozen lake-v 0”vs“frozen lake 8 x8-v 0”)中改变环境外,读者还可以使用不同的超参数值进行测试,如GAMMA, TEST_EPISODE, REWARD_GOALN。你为什么不试试呢?

下一步是什么?

在下一篇文章中,我们将展示学习动作值的值迭代方法代码,而不是像我们在这里所做的那样学习状态值。下一个帖子见

这篇文章的全部代码可以在 GitHub 上找到,并且可以通过这个链接作为 Colab google 笔记本运行。

鸣谢:这篇文章中的代码灵感来自于 Maxim Lapan 的代码,他写了一本关于这个主题的优秀实用书籍

深度强化学习讲解系列

UPC 巴塞罗那理工大学 巴塞罗那超级计算中心

一个轻松的介绍性系列逐渐并以实用的方法向读者介绍这一令人兴奋的技术,这是人工智能领域最新突破性进展的真正推动者。

[## 深度强化学习解释-乔迪托雷斯。人工智能

本系列的内容](https://torres.ai/deep-reinforcement-learning-explained-series/)

关于这个系列

我是在五月份开始写这个系列的,那是在巴塞罗纳的**封锁期。**老实说,由于封锁,在业余时间写这些帖子帮助了我 #StayAtHome 。感谢您当年阅读这份刊物;这证明了我所做的努力。

免责声明 —这些帖子是在巴塞罗纳封锁期间写的,目的是分散个人注意力和传播科学知识,以防对某人有所帮助,但无意成为 DRL 地区的学术参考文献。如果读者需要更严谨的文档,本系列的最后一篇文章提供了大量的学术资源和书籍供读者参考。作者意识到这一系列的帖子可能包含一些错误,如果目的是一个学术文件,则需要对英文文本进行修订以改进它。但是,尽管作者想提高内容的数量和质量,他的职业承诺并没有留给他这样做的自由时间。然而,作者同意提炼所有那些读者可以尽快报告的错误。

价值迭代求解 OpenAI 健身房的 FrozenLake

原文:https://towardsdatascience.com/value-iteration-to-solve-openai-gyms-frozenlake-6c5e7bf0a64d?source=collection_archive---------10-----------------------

素君的承担…

从零开始理解和实现价值迭代…

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在我的叙述下,我们将制定 值迭代 并实现它来解决 FrozenLake8x8-v0 环境来自 OpenAI 的健身房。

这个故事有助于强化学习的初学者 理解 值迭代 从零开始实现,并了解 OpenAI Gym 的 环境。

简介:FrozenLake8x8-v0 环境下,是一个离散有限 MDP。

我们将计算出 最优策略 对于一个*(最佳可能 行动*给定 状态 )在给定 环境下达到目标, 因此得到最大的 预期报酬**

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

哑代理使用随机策略

代理控制一个角色在【8x8】网格世界中的移动。格子的有些瓷砖是可走的【F】,有些导致代理人落水【H】。此外,智能体的运动方向是不确定的(未知策略),并且仅部分取决于所选择的方向(环境动力学)。代理人每走 (0) 步找到一条通往目标方块的可行走路径,将获得 (1) 奖励。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

冰冻湖-v0

*****action _ space:*离散(4),agent 可以采取 4 个离散动作:左(0),下(1),上(2),右(3)。

状态 _ 空间: 离散(64),离散 64 个网格单元。

转移概率 :由于环境的不确定性,以转移概率为例,给定 状态(0)动作(1) 将…

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

环境的属性 : ’ env.env.nA ‘,’ env.env.nS '给出了可能的动作和状态的总数。

P[s|a] = P[s’],s ',r,done

到达后继状态的概率(s ')及其回报®。

更多环境详情, FrozenLake8x8-v0

让我们来理解策略迭代:

预测和控制

策略评估:对于给定的策略(π),确定状态值函数 Vπ(s)。

对于给定的策略 (π) ,初始近似值 v0 被任意选择,对于最终状态为‘0’,值函数的逐次近似值使用贝尔曼方程作为更新规则。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

****贝尔曼期望方程作为更新规则

其中,策略: π(a|s) 是策略 (π) 下状态 (s) 下采取行动 (a) 的概率。环境 跃迁动力学: P(s ',r|s,a) 是到达后继状态 (s’) 并从状态 (s) 采取行动 (a)γ是一个折扣因子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

花一点时间来理解迭代策略评估伪代码****

我们迭代更新规则,直到迭代中的值估计变化变得可以忽略**。**

策略控制:改进现有策略(π)

在我们的例子中,我们贪婪地对待期望值函数,这给了我们确定性的策略。从状态 (s) 中采取具有最高值的动作 (a) ,简单。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

argmax() 函数返回动作,该动作可以将我们带到更高的值状态

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

arg max()的实现

策略迭代:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

策略评估和策略改进将针对每个状态迭代执行,以改进策略和值函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

快速浏览一下策略迭代伪代码****

哥们……

谁使用这种多步多扫的同步 DPs,让我们用“ 【贝尔曼最优性方程】 把这两步结合起来,称之为 【值迭代】 ,并说它属于 【广义策略迭代】

酷……

价值迭代:

所以忘了一切,来个 值迭代 (不,我只是开玩笑……别忘了什么)。

在价值迭代中,我们不运行策略评估直到完成。我们只对所有状态进行一次扫描,并贪婪地使用当前值函数。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现贝尔曼最优方程

当估计值的变化变得可以忽略不计时(伙计,我没这么说,贝尔曼说了。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

****贝尔曼最优性方程作为更新规则

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

实现值函数

因此, 更新规则 不是指任何特定的策略 而是最大化当前值估计 的动作。

唯一等待的事情就是 C 所有的价值函数 和评估 应用策略 :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解决方案:

从策略中,我们从给定的状态中提取价值和要采取的行动。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

确定性策略

我们将策略应用到环境中,并运行 100 集:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

执行政策FrozenLake8x8-v0

结论:

我们看到了价值迭代的公式,并提取了达到目标的最优策略,并在 OpenAI 的 FrozenLake 环境上实现了相同的结果。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

****代理使用最优策略并在的 8 个步骤中达到目标……但受到环境动态的影响

带走:

即使我们得到了一个 严格收敛的最优策略 ,给定了 环境动力学 ,智能体总是达不到目标。

从下一篇文章开始,我们将使用能够更好地制定最优策略的算法。请继续收听更多…

获取算法的完整代码。

参考资料:

[1] 强化学习:导论|第二版,作者理查德·萨顿&安德鲁·g·巴尔托

[2] 大卫·西尔弗的 RL 课程:DeepMind

[3] 开艾健身房

来源:

[4] 强化学习专业:阿尔伯塔大学

[5] 深度强化学习:加州大学伯克利分校

用 Python 评估一家公司

原文:https://towardsdatascience.com/valuing-a-company-with-python-1ddab3e33502?source=collection_archive---------36-----------------------

使用价格销售比

销售价格是分析师用来比较类似股票价值的常用工具。在这篇文章中,我们将使用销售价格比来评估数百家科技行业的公司。这个 Python 脚本将让我们找到按照这个比例最便宜的公司。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

照片由 凯雀罗查发自Pexels

价格与销售额的比率是多少?

销售价格的计算方法是将公司的 市值除以年收入。 它告诉我们需要多少年的收入才能覆盖一家公司目前的市值。

与市盈率不同(点击此处了解如何用 Python 计算 PE 比率),我们可以对没有任何收益的公司使用市盈率。由于这个原因,价格销售比对于那些在成长时期可能没有利润的成长型公司非常有用。

价格销售比本身并不能说明什么。因此,重要的是计算类似公司的行业平均价格销售比,以用作参考。

销售本身可能不足以做出投资决策。除了价格销售比之外,我们还将计算每家公司的毛利率,并结合价格销售比来使用。毛利率告诉我们一家公司在销售成本上赚了多少利润。一个拥有更大毛利率的公司,可以分配更多的资源到研发上来进一步发展公司。或者,它也可以向股东分配更多收益。

毛利率=(销售额—销货成本)/销售额

用 Python 计算销售价格

理论够了。让我们计算一下科技行业的一些公司的价格销售比和毛利率。我们将把我们的财务分析局限于市值超过 100 亿美元的公司。

对于我们的财务分析,我们将使用一个很棒的财务 API fmpcloud 。通过在他们那里开一个账户,你每天可以得到一些免费的 API 调用。让我们构建我们的脚本来逐步计算价格与销售额的比率:

首先,我们得到所有的科技公司,并把它们添加到 Python 列表中。我们使用下面的API终点传递作为参数的技术部门和市值。

import requests 
import pandas as pd

import requests

demo= 'your_api key'

companies = requests.get(f'https://fmpcloud.io/api/v3/stock-screener?sector=technology&marketCapMoreThan=100000000000&limit=100&apikey={demo}')
companies = companies.json()

technological_companies = []

for item in companies:
  technological_companies.append(item['symbol'])

print(technological_companies)
#['MSF.BR', 'MSFT', 'AAPL', 'AMZN', 'GOOG', 'GOOGL', 'FB', 'INCO.BR', 'INTC', ...

然后,我们遍历列表中的每只股票,向 API 发出 http 请求,然后检索损益表数据 。我们解析对的响应,得到收入和毛利率。请注意,我们请求年度损益表来获取年度收入。接下来,我们检索最新市值。最后,我们计算价格与销售额的比率,并将它们添加到一个空字典中。

pricetosales = {}
for item in technological_companies:
    try:
      #annual income statement since we need anual sales
      IS = requests.get(f'https://fmpcloud.io/api/v3/income-statement/{item}?apikey={demo}')
      IS = IS.json()
      Revenue = IS[0]['revenue']
      grossprofitratip = IS[0]['grossProfitRatio']
      #most recent market capitliazation
      MarketCapit = requests.get(f'https://fmpcloud.io/api/v3/market-capitalization/{item}?apikey={demo}')
      MarketCapit = MarketCapit.json()
      MarketCapit = MarketCapit[0]['marketCap']

      #Price to sales
      p_to_sales = MarketCapit/Revenue

      pricetosales[item] = {}
      pricetosales[item]['revenue'] = Revenue
      pricetosales[item]['Gross_Profit_ratio'] = grossprofitratip
      pricetosales[item]['price_to_sales'] = p_to_sales
      pricetosales[item]['Market_Capit'] = MarketCapit
    except:
      pass

print(pricetosales)
#
{'AAPL': {'Gross_Profit_ratio': 0.37817768109,
  'Market_Capit': 1075385951640,
  'price_to_sales': 4.133333659935274,
  'revenue': 260174000000},
 'ADBE': {'Gross_Profit_ratio': 0.850266267202,
  'Market_Capit': 143222958000,
  'price_to_sales': 12.820620380963822,
  'revenue': 11171297000},
 'AMZN': {'Gross_Profit_ratio': 0.409900114786,
  'Market_Capit': 960921360000

最后,我们在 Python 字典中给出了每家公司的价格销售比和毛利比。然而,为了做进一步的分析,在熊猫的数据框架中有这些信息是很好的。我们可以从 _dict 中使用 Pandas DataFrame 方法,并将我们的字典作为参数传递。

price_to_sales_df = pd.DataFrame.from_dict(pricetosales, orient='index')

现在,我们计算技术行业的平均价格销售比,并将其添加到名为 ps_average_sector 的新列中。我们还使用价格销售比作为估价工具来计算额外信息,例如每个公司的价格:

price_to_sales_df['ps_average_sector'] = price_to_sales_df['price_to_sales'].mean()
price_to_sales_df['pscompany_vs_averagesector'] = price_to_sales_df['price_to_sales'] - price_to_sales_df['ps_average_sector']
price_to_sales_df['price_as_per_average_industryPS'] = price_to_sales_df['ps_average_sector'] * price_to_sales_df['revenue']
price_to_sales_df['price_difference'] = price_to_sales_df['price_as_per_average_industryPS'] - price_to_sales_df['Market_Capit']

包扎

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在上面的图片中,我们可以看到我们生成的熊猫数据帧的样本。令人惊讶的是,像苹果和亚马逊这样的公司的价格销售比低于行业平均水平。仅分别为 4.12 和 3.42,而行业平均水平为 5.99。

根据平均行业价格显示如果我们将价格应用于销售行业平均价格作为乘数以获得每只股票的价格,那么股票的市值应该是多少。

例如,通过使用苹果的平均价格销售比(平均 ps 比率*公司收入:5.99 *4.133),我们得到的市值为 1.560851e+12,远低于其当前的真实市值。这是否意味着这是一个购买苹果的好机会?

在做出任何投资决定之前,需要进一步分析其他基本面因素。查看我的Python for Finance——基本面分析,了解其他不错的分析工具。

原载于 2020 年 4 月 5 日【https://codingandfun.com】

R 中的香草神经网络

原文:https://towardsdatascience.com/vanilla-neural-networks-in-r-43b028f415?source=collection_archive---------30-----------------------

看看神经网络架构的引擎盖:从零开始,在 R 中设计并构建一个神经网络,而不使用任何深度学习框架或包

特别感谢:亚历克斯·斯克里文

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图片来源: GitHub

内容:

1.简介
2。背景3。语义
4。设置
5。获取数据
6。检查数据7
准备数据
8。实例化网络
9。初始化网络
10。正向传播11。计算成本
12。反向传播
13。更新模型参数14
端到端运行模型
15。创建预测16
结论
17。帖子脚本

1.介绍

现代数据科学技术经常使用健壮的框架来设计和构建机器学习解决方案。在[R](https://www.r-project.org/)社区中,[tidyverse](https://www.tidyverse.org/)[caret](http://caret.r-forge.r-project.org/)等包被频繁引用;并且在[Python](http://127.0.0.1:17287/rmd_output/0/)内,经常引用[numpy](https://numpy.org/)[pandas](https://pandas.pydata.org/)[sci-kit learn](https://scikit-learn.org/)等包。甚至有一些包已经被构建为可以在两种语言中使用,比如[keras](https://keras.io/)[pytorch](https://pytorch.org/)[tensorflow](https://www.tensorflow.org/)。然而,使用这些包的限制是’黑盒 ’ 现象,用户不理解幕后(或者说“引擎盖下”)发生了什么。用户知道如何使用这些功能,并且可以解释结果,但是不一定知道软件包是如何实现这些结果的。

本文的目的是创建一种“回归基础”的方法来设计深度学习解决方案。其意图不是创建最具预测性的模型,也不是使用最新最棒的技术(如卷积递归);但其意图是创建一个基本的神经网络,从开始,使用没有的框架,并向走过的方法论。

注意:“香草神经网络”中的单词“香草”只是指它是从零开始构建的,并且在其构建中不使用任何预先存在的框架。

2.背景

2.1.语境

已经有很多网站和博客解释了这个过程是如何完成的。如 Jason Brownlee 的文章如何用 Python 编写一个带反向传播的神经网络(从头开始),以及 DeepLearning.ai 的笔记本 dnn_app_utils_v2.py (上微软 Azure 笔记本网)。但是,这些源码都是用 Python 写的。这很好,如果这是所需要的,并且有一些非常合理的理由使用Python而不是其他语言。但是这篇论文会写在R里。

选择R语言有两个原因:

  1. 我熟悉这种语言。我会说Python(还有其他语言);我选择了R来展示如何使用这种语言来实现。
  2. 证明有很多不同的方法可以达到同样的结果。因此,虽然有时选择一种语言比选择另一种语言有合理的限制(业务遗产、技术可用性、系统性能等),但有时选择一种语言只是因为它在风格上更可取。

因此,让我们看看如何在R中设计和构建一个香草神经网络。

2.2.什么不是

本文不涵盖最新的最伟大的深度学习架构(如卷积递归)。因此,如果使用这些其他架构,最终性能可能不会像可能的那样好。

这篇文章没有向读者讲授神经网络如何工作背后的理论数学概念。有很多其他的讲座教授这些信息(例如神经网络背后的数学)。事实上,本文假设读者有很多关于编程、微积分和神经网络概念背后的基础知识。

这篇文章不包括为什么神经网络以这样的方式工作,以及前馈结构背后的概念理解。有很多其他博客(例如神经网络入门)和视频(例如神经网络系列)涵盖了这些信息。

本文并没有向读者指出其他可能已经设置并运行这些信息的包和应用程序。像[tensorflow](https://www.rdocumentation.org/packages/tensorflow)[nnet](https://www.rdocumentation.org/packages/nnet)这样的包已经包含了这一点。

这篇文章实际上是一个功能走查,如何创建一个香草神经网络(一个前馈网络),从零开始,一步一步,用R编程语言。它包含大量代码和技术细节。

3.语义学

3.1.布局

这篇文章就是以这样一种方式来描述神经网络是如何从头开始构建的。它将完成以下步骤:

  1. 访问并检查数据
  2. 实例化和初始化网络
  3. 向前传播
  4. 计算成本
  5. 反向传播
  6. 更新模型
  7. 建立一个训练方法来循环每一件事
  8. 预测和评估绩效

为了简洁起见,这里定义的函数将不包括典型函数中应该包括的所有注释和验证。它们将只包括基本步骤和提示。然而,本文的源代码(位于这里)确实包含了所有适当的函数文档字符串和断言。

3.2.句法

在很大程度上,本文中的语法保留了[dplyr](https://www.rdocumentation.org/packages/dplyr)‘pipe’方法(使用了%>%符号)。然而,在某些部分使用了 R [base](https://www.rdocumentation.org/packages/base)语法(例如,在函数声明行中)。

在整篇文章中,编写了许多自定义函数。这些都带有前缀getletset。每个的定义如下。

  • get_*() :
    —它将get将来自对象的元数据的某些属性解析到此函数。
    —或将使用解析到该函数的信息来导出和get其他值或参数。
  • set_*() :
    —它将set(或‘更新’)解析到该函数的对象。
    —通常用于在正向和反向传播过程中更新网络。
  • let_*() :
    —与get类似,它使用解析到该函数的其他值来导出结果,但是let该值将被另一个对象或函数使用。
    —主要用于初始化和激活功能。

4.建立

4.1.加载包

第一步是导入相关的包。此列表包括整个过程中使用的主要软件包;并列出其主要用途。

请注意上面列出的关于不使用现有深度学习包的内容,然而tensorflow包却包括在内。为什么?嗯,这仅用于访问数据,这将在下一节讨论。tensorflow包不用于构建和训练任何网络。

library(tensorflow)  #<-- Only used for getting the data
library(tidyverse)   #<-- Used for accessing various tools
library(magrittr)    #<-- Extends the `dplyr` syntax
library(grDevices)   #<-- For plotting the images
library(assertthat)  #<-- Function assertions
library(roxygen2)    #<-- Documentation is important
library(caret)       #<-- Doing data partitioning
library(stringi)     #<-- Some string manipulation parts
library(DescTools)   #<-- To properly check `is.integer`
library(tictoc)      #<-- Time how long different processes take
library(docstring)   #<-- Makes viewing the documentation easier
library(roperators)  #<-- Conveniently using functions like %+=%
library(plotROC)     #<-- For plotting predictions

5.检索数据

5.1.下载数据

要使用的数据集是 CIFAR-10 数据集。选择它有很多原因,包括:

  1. 数据在图像上,非常适合深度学习目的;
  2. 包含了相当数量的图像(总共 60,000 张图像);
  3. 所有图像都是相同的大小(32x32 像素);
  4. 这些图像被分为 10 个不同的类别;和
  5. 通过TensorFlow包可以轻松访问它。

以下代码块具有以下过程步骤:

  1. 获取数据
    —为了导入日期,通过keras元素访问,该元素包含datasets的套件,包括cifar10部分。
    load_data()函数从在线 GitHub 存储库中检索数据。
  2. 提取第二个元素
    —这里的load_package()返回两个不同的对象:
    — — 1。训练数据集(包含 50,000 幅图像);
    — — 2。测试数据集(包含 10,000 张图像)。
    —提取第二个元素(通过使用extract2(2)功能),因为只需要 10,000 张图像。
    —本文将展示创建香草神经网络的过程;如果以后需要更多数据,可以在这里轻松访问。
  3. 说出零件的名称
    —下载的数据包含另外两个元素:
    — — 1。图像本身(以 4 维阵列的形式);
    ———2。图像标签(以二维单列数组的形式)。
    —该数据没有任何名称,因此使用set_names()函数设置名称。
# Download Data
# NOTE:
# - The first time you run this function, it download everything.
# - Next time you run it, TensorFlow will load from Cache.
cifar <- tf$keras$datasets$cifar10$load_data() %>% 
    extract2(2) %>% 
    set_names(c("images","classes"))

5.2.获取类定义

TensorFlow包中访问这些数据的一个挑战是,对于每种类型的图像,这些类都只是数值(09)。这些图像的定义可以在 GitHub 上找到(GitHub>EN10>CIFAR)。这些类在下面的代码块中定义。

# Define classes
ClassList <- c(
    "0" = "airplane",
    "1" = "automobile",
    "2" = "bird",
    "3" = "cat",
    "4" = "deer",
    "5" = "dog",
    "6" = "frog",
    "7" = "horse",
    "8" = "ship",
    "9" = "truck" 
)

6.检查数据

6.1.检查对象

检查数据是很重要的,以确保数据是正确生成的,并且所有信息看起来都没问题。为此,编写了一个自定义函数(get_ObjectAttributes()),其源代码可以在这里找到。如下面的代码块所示,images 对象是一个四维数字数组,包含10,000个图像,每个32 x 32个像素,以及3个颜色通道。整个物体超过117 Mb大。

# Check Images
cifar %>% 
    extract2("images") %>% 
    get_ObjectAttributes("cifar$images") %>% 
    cat()

它打印:

Name : cifar$images
 - Size : 117.2 Mb
 - Clas : array
 - Type : integer
 - Mode : numeric
 - Dims : 10000x32x32x3

当检查classes 对象时,它是一个 2 维数字数组(只有 1 列),但是具有与images 对象相同数量的图像(这是预料中的),每个类标签的频率恰好都具有1000图像。总尺寸小于40 Kb

# Check classes
cifar %>% 
    extract2("classes") %>% 
    get_ObjectAttributes(name="cifar$classes", print_freq=TRUE) %>% 
    cat()

它打印:

Name : cifar$classes
 - Size : 39.3 Kb
 - Clas : matrix,array
 - Type : integer
 - Mode : numeric
 - Dims : 10000x1
 - Freq :
      label Freq
   1  0     1000
   2  1     1000
   3  2     1000
   4  3     1000
   5  4     1000
   6  5     1000
   7  6     1000
   8  7     1000
   9  8     1000
   10 9     1000

6.2.检查图像

在了解了内存中对象的大小之后,就有必要检查实际的图像本身了。作为人类,我们理解实际的图像和颜色,胜过理解数字。

为了可视化图像,编写了两个自定义函数,如下面的代码块所示。这些函数接收数据(作为一个 4 维数组),并将图像可视化为一个图。

set_MakeImage <- function(image, index=1) {

    # Extract elements
    image.r <- image[,,1]
    image.g <- image[,,2]
    image.b <- image[,,3]

    # Make rgb
    image.rgb <- rgb(
        image.r, 
        image.g, 
        image.b, 
        maxColorValue=255
    )

    # Fix dimensions
    dim(image.rgb) <- dim(image.r)

    # Return
    return(image.rgb)

}plt_PlotImage <- function(images, classes, class_list, index=1) {

    # Slice images
    image <- images[index,,,]
    image %<>% set_MakeImage(index)
    lbl <- classes %>% 
        extract(index) %>% 
        as.character() %>% 
        class_list[[.]]

    # Create plot
    plot <- ggplot() + 
        ggtitle(lbl) +
        draw_image(image, interpolate=FALSE)

    # Return
    return(plot)

}

对前 16 幅图像运行该功能时,将显示以下内容。如图所示,这些图像非常像素化(这是意料之中的,因为它们每个只有32 x 32像素),你可以看到每个图像是如何分类的。

# Set list
lst <- list()# Loop 16 images
for (index in 1:16) {
    lst[[index]] <- plt_PlotImage(
        cifar$images, 
        cifar$classes, 
        ClassList,
        index)
    }# View images
plt <- gridExtra::grid.arrange(grobs=lst, ncol=4)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 1 :初始图像

7.准备数据

准备数据有四个步骤:

  1. 把…重新分类
  2. 裂开
  3. 使再成形
  4. 使标准化

7.1.把…重新分类

出于本文的目的,让我们假设我们正在尝试预测图片是car还是not。这将需要将数据转换为二进制分类问题,其中神经网络将从数据中预测10。这将意味着模型输出将是分数的概率分布,通过改变截止变量可以容易地对其进行分类。

第一步是对数据进行重新分类,使所有汽车的值都是1,其他的都是0。我们从之前定义的类中知道,汽车已经有了值1,这意味着只需要对所有其他类进行转换。

# Implement within a pipe
cifar[["classes"]] <- cifar %>%
    extract2("classes") %>% 
    (function(classes){

        # View initial classes
        classes %>% as.vector %>% head(40) %>% print

        # Reclassify
        classes <- ifelse(classes==1,1,0)

        # View reclassified classes
        classes %>% as.vector %>% head(40) %>% print

        # Return
        return(classes)
    })

它打印:

[1] 3 8 8 0 6 6 1 6 3 1 0 9 5 7 9 8 5 7 8 6 7 0 4 9 5 2 4 0 9 6
[1] 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

7.2.分割数据

下一个任务是将数据分成训练集和测试集。这样做的原因在其他地方有所涉及(如维基百科的交叉验证谷歌的机器学习速成班:训练和测试集分割数据)。

首先,为了理解当前的数据分割,下面的代码块使用ggplot2包可视化了这些数据。如图所示,数据目前分布在90%0类别中,剩余的10%1类别中。

# Print Plot
cifar %>% 
    extract("classes") %>% 
    table(dnn="classes") %>% 
    data.frame() %>% 
    ggplot(aes(classes, Freq, fill=classes)) + 
        geom_col(colour="black") +
        geom_label(
            aes(label=Freq),
            show.legend=FALSE
        ) +
        scale_y_continuous(breaks=seq(0,10000,1000)) +
        theme(panel.grid.minor.y=element_blank()) +
        labs(
            title="Count of Each Class"
        )

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 2 :每一类的计数

为了实现这种数据分割,我们使用了caret::createDataPartition()函数。这将创建一个partition对象,然后使用它来相应地分离cifar数据。在70%任意选择拆分比例用于训练,其余用于测试。然而,这可能是合理的80%;这是一个超参数,可以在以后的阶段调整。

# Set seed for reproducibility
set.seed(1234)# Create partition
partition <- createDataPartition(cifar$classes, p=0.7, list=FALSE)# Split data
trn_img <- cifar$images[partition,,,]
tst_img <- cifar$images[-partition,,,]
trn_cls <- cifar$classes[partition]
tst_cls <- cifar$classes[-partition]

分割后,数据被重新绘制,很容易看出训练/测试分割在两个类别上实现了均匀的70%分布。

# Print Plot
rbind(
    trn_cls %>% table,
    tst_cls %>% table
) %>% 
    set_rownames(c("train","test")) %>% 
    data.frame() %>% 
    rename_all(str_remove_all, "X") %>% 
    rownames_to_column("data") %>% 
    pivot_longer(2:3, names_to="classes", values_to="Freq") %>% 
    mutate(
        label=paste(data, classes, sep=": "),
        data=factor(data, levels=c("train","test"))
    ) %>% 
    ggplot(aes(classes, Freq, fill=data), position="dodge") + 
        geom_col(colour="black", position="dodge") +
        geom_label(
            aes(label=Freq), 
            position=position_dodge(width=0.9),
            show.legend=FALSE
        ) +
        scale_y_continuous(breaks=seq(0,10000,1000)) +
        theme(panel.grid.minor.y=element_blank()) +
        labs(
            title="Count of Each Class",
            subtitle="Split by Train/Test"
        )

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 3 :每类计数,按训练/测试划分

检查数据是否被正确分割的另一种方法是再次运行get_ObjectAttributes()函数,如下面的代码块所示。这里显示的信息与上面的图一致。有趣的是,训练图像数组82 Mb很大,这对于以后检查正向传播的性能很重要。

for (name in c("trn_img","tst_img","trn_cls","tst_cls")) {
    name %>% 
        get() %>% 
        get_ObjectAttributes(
            name, 
            if (name %in% c("trn_cls","tst_cls")) TRUE else FALSE
        ) %>% 
        cat()
    if (name != "tst_cls") cat("\n")
}

它打印:

Name : trn_img
 - Size : 82 Mb
 - Clas : array
 - Type : integer
 - Mode : numeric
 - Dims : 7000x32x32x3Name : tst_img
 - Size : 35.2 Mb
 - Clas : array
 - Type : integer
 - Mode : numeric
 - Dims : 3000x32x32x3Name : trn_cls
 - Size : 54.7 Kb
 - Clas : numeric
 - Type : double
 - Mode : numeric
 - Dims : 7000
 - Freq :
     label Freq
   1 0     6309
   2 1      691Name : tst_cls
 - Size : 23.5 Kb
 - Clas : numeric
 - Type : double
 - Mode : numeric
 - Dims : 3000
 - Freq :
     label Freq
   1 0     2691
   2 1      309

7.3.重塑数据

对于我们神经网络的第一个input层,我们希望是一个一维的节点。因此,有必要将数据从一个 4 维数组调整为一个 2 维数组。这个过程叫做展平,更多信息可以在这里找到:多维数组的内存布局

使用array()函数可以很容易地实现该方法,因为它有dim=参数,可以用来指定所需的尺寸。

期望的矩阵尺寸应该使每个图像在新的一行,每个像素在不同的一列。因为每个像素由第 2、第 3 和第 4 维组成,我们需要取这三个数的乘积,并使用它来指定所需的列数。实际上,我们正在运行这个等式:32 × 32 × 3,这相当于拥有3072列。这个等式是在下一个代码块中以编程方式内嵌实现的。

# Reshape data
trn_img %<>% array(dim=c(
    dim(.) %>% extract(1),
    dim(.) %>% extract(2:4) %>% prod()
))tst_img %<>% array(dim=c(
    dim(.) %>% extract(1),
    dim(.) %>% extract(2:4) %>% prod()
))trn_cls %<>% array(dim=c(
    length(.),
    1
))tst_cls %<>% array(dim=c(
    length(.),
    1
))

当再次检查对象属性时,您将看到图像数据已经被正确地处理,以行数作为图像的数量,以列数作为像素的数量。

for (name in c("trn_img","tst_img","trn_cls","tst_cls")) {
    name %>% 
        get() %>% 
        get_ObjectAttributes(name, FALSE) %>% 
        cat()
    if (name != "tst_cls") cat("\n")
}

它打印:

Name : trn_img
 - Size : 82 Mb
 - Clas : matrix,array
 - Type : integer
 - Mode : numeric
 - Dims : 7000x3072Name : tst_img
 - Size : 35.2 Mb
 - Clas : matrix,array
 - Type : integer
 - Mode : numeric
 - Dims : 3000x3072Name : trn_cls
 - Size : 54.9 Kb
 - Clas : matrix,array
 - Type : double
 - Mode : numeric
 - Dims : 7000x1Name : tst_cls
 - Size : 23.6 Kb
 - Clas : matrix,array
 - Type : double
 - Mode : numeric
 - Dims : 3000x1

7.4.标准化数据

准备数据的最后一步是标准化数据,以便所有元素都在01之间。这样做的原因是为了防止在后面的步骤中出现爆炸和消失的梯度,因为神经网络将试图拟合所有的波峰和波谷,这是由于数据在0255的值范围内引起的。

TensorFlow 网站所述, CIFAR10 数据集由 RGB 图像数据组成。并且,正如维基百科上记载的,RGB 数据都是在0255之间的值。

所以要做的就是把所有的元素除以255,必然会产生一个在01之间的值。由于图像数据当前在一个数组中,下面代码块中的函数将作为一个矢量化函数在整个数组中运行,相应地将所有元素除以255

trn_img <- trn_img/255
tst_img <- tst_img/255

数据现已准备好,可用于网络。下一步是建立网络。

8.实例化网络

8.1.定义架构

关于网络实际上是什么的一些快速注释:

  • 整体架构是一个list
  • list的每一个element都是另一个list,这些将构成整体网络的每一个
  • 第一层永远是input层。
  • 最后一层将永远是output层。
  • 中间的每一层都将是hidden层,这些层的名称简单地用数字命名。
  • 每层的每个元素都将被标记为相同,定义如下:
  • nodz:本层节点数。
  • inpt:输入矩阵。又名A_prev。这是前一层激活的副本,因此对于大型网络,需要考虑这一点。
  • wgts:权重矩阵。又名W
  • bias:偏置向量。又名b
  • linr:线性矩阵。又名Z。这是inptwgtsbias之间的线性代数的结果。
  • acti:激活矩阵。又名A。将激活函数应用于linr矩阵的结果。
  • acti_func:使用的激活功能。
  • cost:车型的整体成本。这是一个单一的值(模型的总成本),但被复制到模型的每一层。
  • back_cost:成本向量的梯度。又名dA_cost
  • back_acti:激活矩阵的梯度。又名dA。应用反向传播后的微分结果。具有给定的成本函数。
  • back_linr:线性代数矩阵的梯度。又名dZ。向后线性微分反向传播的结果。
  • back_wgts:权重矩阵的梯度。又名dW。也是背撑的结果。
  • back_bias:偏置向量的梯度。又名db。也是背撑的结果。

8.1.设置实例化功能

对于下面的代码块,定义了函数set_InstantiateNetwork()。它只有三个输入参数,用于指定每层中使用的节点数。基于这些信息,模型将被实例化并返回,为下一步的初始化做好准备。

set_InstantiateNetwork <- function(
    input=50, 
    hidden=c(30,20,10), 
    output=1
    ) { # Set up
    model = list()
    layers = c(
        "input",
        1:length(hidden),
        "output"
    )

    # Loop
    for (layer in layers) {

        # Make layer
        model[[layer]] <- list(
            "nodz"      = "",
            "inpt"      = "",
            "wgts"      = "",
            "bias"      = "",
            "linr"      = "",
            "acti"      = "",
            "acti_func" = "",
            "cost"      = "",
            "back_cost" = "",
            "back_acti" = "",
            "back_linr" = "",
            "back_wgts" = "",
            "back_bias" = "" 
        )

        # Set nodes
        if (layer=="input") {
            model[[layer]][["nodz"]] <- input
        } else if (layer=="output") {
            model[[layer]][["nodz"]] <- output
        } else {
            layer_index <- layer %>% as.numeric()
            model[[layer]][["nodz"]] <- hidden[layer_index]
        }

    }

    # Return
    return(model)

}

8.1.创建网络

下面的代码块实例化了网络。该模型将在每层中设置以下数量的节点:

  • 层将有 T2 节点,和上面计算的一样多。
  • 10020音符,每个hidden层的节点数量会逐渐减少。
  • output层会有1节点,因为这一个节点会是01之间的浮点数,用来预测相关图像是不是汽车。
network_model <- set_InstantiateNetwork(
    input=3072, 
    hidden=c(100,75,50,30,20), 
    output=1
)

8.1.将网络可视化

有一个非常好的网站可以让神经网络可视化:http://alexlenail.me/NN-SVG/AlexNet.html。下图是刚刚创建的网络的示意图。

一旦网络完全初始化并向前传播,可以进行进一步的可视化。详见 章节检查模型形状

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 4 :网络的可视化

9.初始化网络

初始化网络有四个步骤:

  1. 设置重量初始化功能
  2. 设置层初始化功能
  3. 设置模型初始化功能
  4. 运行初始化

9.1.重量初始化

在其核心,权重初始化只是生成一个随机的正常数字(用μ=0σ=1)。然而,通过仅使用这个随机生成的数字,当试图训练更深的神经网络时,发现模型梯度爆炸或消失。因此,这些权重在被初始化后需要被缩放,以便足够健壮以继续在更深的层被训练。

有许多算法可用于重量初始化。两个比较常见的是 Xavier 算法和 He 算法。理解这些算法背后的细节的一些好资源包括:

9.1.1。泽维尔算法

Xavier 初始化的公式为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

等式 1 : Xavier 初始化算法

其中:

  • nᵢ 是进入这一层的节点数量。也称为“扇入”。
  • nᵢ₊₁ 是从这一层出去的节点数。也称为“扇出”。

9.1.2。何算法

he 初始化的公式为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

等式 2 :初始化算法

其中:

  • nᵢ是进入这一层的节点数量。

9.1.初始化功能

出于编程目的,这些函数是用order值作为函数参数的一部分编写的。这意味着方程的数量级可以在稍后阶段改变,并用作超参数。

let_InitialiseXavier <- function(nodes_in, nodes_out, order=6) {

    # Do work
    numer <- sqrt(order)
    denom <- sqrt(nodes_in + nodes_out)
    output <- numer/denom

    # Return
    return(output)

}let_InitialiseHe <- function(nodes_in, nodes_out, order=2) {

    # Do work
    numer <- order
    denom <- nodes_in
    output <- sqrt(numer/denom)

    # Return
    return(output)

}

9.3.层初始化

下一步是构建一个函数,该函数将初始化一个单独层的所有相关方面。这一步很重要,因为这是创建权重矩阵的地方,并且这些权重矩阵必须以某种方式构建,以确保维度允许成功的向前传播。

层构造的步骤如下:

  1. 确定当前层的层名(layer)和前一层的层名(layer_prev)。
    —用于从network_model列表中访问相关配置。
  2. 确定馈入本层的节点数(nodes_in)和馈出当前层的节点数(nodes_out)。
    —用于解析初始化算法。
  3. 创建权重矩阵。
    —尺寸如下:
    ———1。行数是前一层中的节点数。
    ———2。列数是当前层中的节点数。
    —使用rnorm()函数创建每个元素,该函数使用μ=0σ=1在正常曲线上生成一个随机数。
  4. 确定要使用的初始化算法。
    —解析到该函数中的 value 值是与相关算法相关的小写单词。
    —强制转换为标题大写,然后赋予前缀let_Initialise
    —该值然后被解析到get()函数中,该函数然后调用该函数,并执行解析到该函数的参数。
    —这是一种灵活调用不同算法的编程方式,基于解析到函数的值。
  5. 缩放权重矩阵。
    —通过将每个元素乘以初始化算法。
  6. 创建偏差矩阵。
    —尺寸如下:
    — — 1。行数是当前层中的节点数。
    ———2。只有一列。
    —每个元素都有值0
  7. 将重量和偏差矩阵重新应用到network_model上。
  8. 返回更新后的network_model对象。

为了实现这一点,函数使用的函数参数包括:

  1. network_model本身。
  2. 该层的layer_index(其中1input层,每个递增的数字为每个后续层)。
  3. initialisation_algorithm,或者是值NA,或者是值xavier或者是相关算法的值he
  4. initialisation_order,它是一个整数值,在相关算法中用作分子。
set_InitialiseLayer <- function(
    network_model,
    layer_index,
    initialisation_algorithm=NA,
    initialisation_order=6
    ) {

    # Get layer names
    layer_prev <- names(network_model)[layer_index-1]
    layer <- names(network_model)[layer_index]

    # Get number of nodes
    if (layer_index == 1) {
        nodes_in <- 0 #First layer is 'input'
    } else {
        nodes_in <- network_model %>% 
            extract2(layer_prev) %>% 
            extract2("nodz")
    }
    nodes_out <- network_model %>% 
        extract2(layer) %>% 
        extract2("nodz")

    # Set the seed of reproducibility
    set.seed(1234)

    # Initialise weight matrix
    w_matrix <- matrix(
        data=rnorm(nodes_in * nodes_out), 
        nrow=nodes_in,
        ncol=nodes_out
    )

    # Get initialisation algorithm
    if (!is.na(initialisation_algorithm)) {
        algorithm <- paste0(
            "let_Initialise", 
            str_to_title(initialisation_algorithm)
        )
    }

    # Scale weights
    if (layer_index != 1) {
        if (is.na(initialisation_algorithm)) {
            w_matrix <- w_matrix
        } else {
            w_matrix <- w_matrix * 
                get(algorithm)(
                    nodes_in=nodes_in, 
                    nodes_out=nodes_out, 
                    order=initialisation_order
                )
        }
    }

    # Initialise bias matrix
    b_matrix <- matrix(
        data=network_model %>% 
            extract2(layer) %>% 
            extract2("nodz") %>% 
            replicate(0),
        nrow=network_model %>% 
            extract2(layer) %>% 
            extract2("nodz"),
        ncol=1
    )

    # Place data back in to the model
    network_model[[layer]][["wgts"]] <- w_matrix
    network_model[[layer]][["bias"]] <- b_matrix

    # Return
    return(network_model)

}

9.4.模型初始化

set_InitialiseModel()功能的目的是循环通过network_model对象中的每一层,根据模型本身的参数设置对其进行初始化。该功能将获取节点数(由set_InstantiateNetwork()功能设置)。

该函数将只接受三个输入参数:

  1. network_model :
    —由set_InstantiateNetwork()函数实例化的网络。
  2. initialisation_algorithm :
    —用于网络初始化的算法。
    —这应该是值NAxavierhe
  3. initialisation_order :
    —用于等式分子的顺序值。
    —这可以是一个数值,或者是字符串layers,表示顺序应该是网络中隐藏层的数量。
set_InitialiseModel <- function(
    network_model, 
    initialisation_algorithm="xavier", 
    initialisation_order="layers"
    ) {

    # Redefine 'initialisation_order'
    if (initialisation_order == "layers") {
        initialisation_order <- get_CountOfElementsWithCondition(
            names(network_model), 
            function(x){is.integer(as.numeric(x))}
        )
    }

    # Initialise each layer
    for (layer_index in 1:length(names(network_model))) {
        network_model <- set_InitialiseLayer(
            network_model=network_model, 
            layer_index=layer_index, 
            initialisation_algorithm=initialisation_algorithm,
            initialisation_order=initialisation_order
        )
    }

    # Return
    return(network_model)

}

注意,该函数使用了用户自定义函数get_CountOfElementsWithCondition()。该函数允许计算模型中隐藏层的数量。该功能的源代码可以在这里找到。

9.5.网络初始化

下面的代码块使用上面定义的函数初始化网络。使用的方法是%<>%,它在magrittr包中定义,说明它用于更新左侧的值,首先通过管道将它输入右侧的第一个参数位置,然后将结果赋回左侧的对象。

network_model %<>% set_InitialiseModel()

9.6.检查模型参数

为了快速检查,下面的代码块检查已定义模型的参数数量。这再次使用了一个定制函数get_ModelParametersCount(),它的定义可以在本文的源代码中找到(位于这里)。

network_model %>% 
    get_ModelParametersCount() %>% 
    format(big.mark=",")

它打印:

[1] "320,846"

该神经网络中有超过320,000个参数。这些参数中的每一个都需要训练,并且每一个都会对最终结果产生影响。在接下来的部分中,我们将继续介绍如何实现这一点。

10.正向传播

10.1.该理论

有一个非常好的网站概述了矩阵乘法中实际发生的事情:矩阵乘法

正向传播方法的理论如下:

  1. 应用矩阵乘法
    — 1。第一个矩阵是来自前一层的激活矩阵。
    — 2。第二个矩阵是当前层的权重矩阵。
    — 3。第三矩阵是当前层的(第一)线性激活矩阵。
  2. 应用偏置矩阵
    — 1。第一矩阵是当前层的(第一)线性激活。
    — 2。第二个矩阵是当前层的偏差矩阵。
    — 3。第三矩阵是当前层的(第二)线性激活矩阵。
  3. 应用激活算法
    — 1。第一矩阵是当前层的(第二)线性激活矩阵。
    — 2。激活功能由用户在功能运行期间确定。可以是relu激活、sigmoid激活或任何其他激活功能。
    — 3。第三个矩阵是当前层的激活矩阵。

10.1.1。第一步

为了说明,下面的矩阵显示了正向传播的第一步,使用了虚拟数字。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图五:前进道具,第一步

下面的代码显示了这个过程是如何以编程方式实现的。

# Declare First matrix
matrix_input <- matrix(
    data=1:24, 
    nrow=8, 
    ncol=3, 
    byrow=TRUE
)# Declare Weight matrix
matrix_weight <- matrix(
    data=1:15, 
    nrow=3, 
    ncol=5, 
    byrow=TRUE
)# Apply matrix manipulation
matrix_layer <- matrix_input %*% matrix_weight

它打印:

matrix_input:
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9
[4,]   10   11   12
[5,]   13   14   15
[6,]   16   17   18
[7,]   19   20   21
[8,]   22   23   24matrix_weight:
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    2    3    4    5
[2,]    6    7    8    9   10
[3,]   11   12   13   14   15matrix_layer:
     [,1] [,2] [,3] [,4] [,5]
[1,]   46   52   58   64   70
[2,]  100  115  130  145  160
[3,]  154  178  202  226  250
[4,]  208  241  274  307  340
[5,]  262  304  346  388  430
[6,]  316  367  418  469  520
[7,]  370  430  490  550  610
[8,]  424  493  562  631  700

10.1.2。第二步

下图显示了如何应用偏置矩阵。如图所示,偏置矩阵中的每个元素水平应用于初始矩阵的每个元素。这张图表显示了它是如何工作的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 6 :前进支柱,第二步

下面的代码显示了这个过程是如何以编程方式实现的。

# Declare Bias matrix
vector_bias <- matrix(1:8, 8, 1)# Apply Bias matrix
matrix_biased <- sweep(matrix_layer, 1, vector_bias, "+")

它打印:

matrix_layer:
     [,1] [,2] [,3] [,4] [,5]
[1,]   46   52   58   64   70
[2,]  100  115  130  145  160
[3,]  154  178  202  226  250
[4,]  208  241  274  307  340
[5,]  262  304  346  388  430
[6,]  316  367  418  469  520
[7,]  370  430  490  550  610
[8,]  424  493  562  631  700vector_bias:
     [,1]
[1,]    1
[2,]    2
[3,]    3
[4,]    4
[5,]    5
[6,]    6
[7,]    7
[8,]    8matrix_biased:
     [,1] [,2] [,3] [,4] [,5]
[1,]   47   53   59   65   71
[2,]  102  117  132  147  162
[3,]  157  181  205  229  253
[4,]  212  245  278  311  344
[5,]  267  309  351  393  435
[6,]  322  373  424  475  526
[7,]  377  437  497  557  617
[8,]  432  501  570  639  708

10.1.3。第三步

激活功能是在运行该功能时定义的功能。该算法应用于初始矩阵的每个元素。在这种情况下,使用初始矩阵的简单乘法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7 :前进道具,第三步

下面的代码显示了这个过程是如何以编程方式实现的。

# Apply Activation function
matrix_output <- matrix_biased * (0.01 * matrix_biased)

它打印:

matrix_biased:
     [,1] [,2] [,3] [,4] [,5]
[1,]   47   53   59   65   71
[2,]  102  117  132  147  162
[3,]  157  181  205  229  253
[4,]  212  245  278  311  344
[5,]  267  309  351  393  435
[6,]  322  373  424  475  526
[7,]  377  437  497  557  617
[8,]  432  501  570  639  708matrix_output:
        [,1]    [,2]    [,3]    [,4]    [,5]
[1,]   22.09   28.09   34.81   42.25   50.41
[2,]  104.04  136.89  174.24  216.09  262.44
[3,]  246.49  327.61  420.25  524.41  640.09
[4,]  449.44  600.25  772.84  967.21 1183.36
[5,]  712.89  954.81 1232.01 1544.49 1892.25
[6,] 1036.84 1391.29 1797.76 2256.25 2766.76
[7,] 1421.29 1909.69 2470.09 3102.49 3806.89
[8,] 1866.24 2510.01 3249.00 4083.21 5012.64

10.2.线性分量

当组合在一起时,线性代数函数只用三行代码就实现了,如下面的set_LinearForward()函数中的代码块所示。

set_LinearForward <- function(inpt, wgts, bias) {

    # Perform matrix multiplication
    linr <- inpt %*% wgts

    # Add bias
    linr <- sweep(linr, 2, bias, "+")

    # Return
    return(linr)

}

10.3.非线性分量

神经网络的真正力量来自于它们的激活函数。现在,网络能够捕捉非线性方面,这就是强调其预测能力的原因。

根据网络的目的,激活功能可以是许多不同算法中的一种。激活的选择可以是在稍后阶段选择的超参数。 Desmos 网站提供了一种极好的交互方式来查看不同类型的激活:激活功能

每个激活函数都是单独定义的,并且每个函数只接受一个参数,这是一个将被激活的矩阵。为了简洁起见,这里提供了四种比较流行的激活方式;但是还有很多很多其他的方法可以使用。还提供了如何计算这些函数的资源和等式:

**sigmoid** :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方程式 3 :乙状结肠激活

来源:
1。如何在 Python 中计算一个 logistic sigmoid 函数
2。使用 Numpy 实现 sigmoid 函数

**relu** :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方程式 4 : Relu 激活

来源:
1。带 Sigmoid、ReLu 和 Softmax 激活功能的 NumPy 初学者指南

**softmax**T25:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方程式 5: Softmax 激活

来源:
1。 Softmax 激活功能说明

**swish** :

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方程式 6 :嗖嗖激活

来源:
1。搜索激活功能
2。在 Keras 中实现 Swish 激活功能

这些激活函数以编程方式定义如下:

let_ActivateSigmoid <- function(linr) {

    # Do work
    acti <- 1/(1+exp(-linr))

    # Return
    return(acti)

}let_ActivateRelu <- function(linr) {

    # Do work
    acti <- sapply(linr, max, 0) %>% 
        structure(dim=dim(linr))

    # Return
    return(acti)

}let_ActivateSoftmax <- function(linr) {

    # Do work
    expo <- exp(linr)
    expo_sum <- sum(exp(linr))
    acti <- expo/expo_sum

    # Return
    return(acti)

}let_ActivateSwish <- function(linr, beta=0.1) {

    # Do work
    acti <- linr * (beta * linr)

    # Return
    return(acti)

}

10.4.设置正向传播函数

set_ForwardProp()函数集合了上面提到的所有组件。它执行以下步骤:

  1. 循环通过network_model的每一层。
  2. 获取当前图层的图层名称。
  3. input层实施一个“通过”过程。
  4. 提取相关信息,包括:
    —1。上一层的层名
    —2。上一层的激活矩阵
    —3。当前
    — 4 的权重矩阵。电流层的偏置矩阵
  5. 应用线性代数组件。
  6. 应用非线性激活组件。
  7. 请注意隐藏层的激活和最终层的激活之间的差异。
  8. 将相关信息应用回网络。
  9. 返回network_model对象。

为了实现这个过程,这个函数只有四个参数:

  1. network_model:待更新的网络模型。
  2. data_in:训练图像的 4 维数组,如上定义。
  3. activation_hidden:应用于隐藏层的激活功能。
  4. activation_final:应用于最终(output)层的激活功能。
set_ForwardProp <- function(
    network_model, 
    data_in, 
    activation_hidden="relu", 
    activation_final="sigmoid"
    ) {

    # Do work
    for (index in network_model %>% names() %>% length() %>% 1:.) {

        # Define layer name
        layr <- network_model %>% 
            names() %>% 
            extract(index)

        if (layr=="input") {

            # Pass-thru for 'input' layer
            network_model[[layr]][["inpt"]] <- data_in
            network_model[[layr]][["acti"]] <- data_in

        } else {

            # Extract data
            prev <- names(network_model)[index-1]
            inpt <- network_model %>% 
                extract2(prev) %>% 
                extract2("acti")
            wgts <- network_model %>% 
                extract2(layr) %>% 
                extract2("wgts")
            bias <- network_model %>% 
                extract2(layr) %>% 
                extract2("bias")

            # Calculate
            linr <- set_LinearForward(inpt, wgts, bias)

            # Activate
            if (layr=="output") {
                func <- activation_final %>% 
                    str_to_title() %>% 
                    paste0("let_Activate", .) %>% 
                    get()
                acti <- func(linr)
                network_model[[layr]][["acti_func"]] <- activation_final
            } else {
                func <- activation_hidden %>% 
                    str_to_title() %>% 
                    paste0("let_Activate", .) %>% 
                    get()
                acti <- func(linr)
                network_model[[layr]][["acti_func"]] <- activation_hidden
            }

            # Apply back to our model
            network_model[[layr]][["inpt"]] <- inpt
            network_model[[layr]][["linr"]] <- linr
            network_model[[layr]][["acti"]] <- acti

        }

    }

    # Return
    return(network_model)

}

10.5.向前传播

正向传播过程的最后一步是实际运行函数。在下面的代码块中,实现了tic()toc()函数来计算进程运行的时间。

tic()
network_model %<>% set_ForwardProp(trn_img, "relu", "sigmoid")
toc()

它打印:

7.05 sec elapsed

如上面 分割数据 部分所述,trn_img对象超过82 Mb大,模型超过320,000参数。向前传播的整个端到端过程仅用了7 seconds来运行;令人印象深刻,证明了数学的力量。

10.6.检查模型形状

现在可以使用自定义功能get_PrintNetwork()打印网络。这个函数在一个单独的盒子中返回每一层,包含关键信息,如矩阵的相关形状和使用的激活函数。该功能的源代码可以在这里找到。

# Print the Network
network_model %>% 
    get_PrintNetwork() %>% 
    cat()

它打印:

+--------------------------------+
|      Layer : input             |
|      Nodes : 3,072             |
| Inpt Shape : 7,000 x 3,072     |
| Wgts Shape :     0 x 3,072     |
| Outp Shape : 7,000 x 3,072     |
| Activation :                   |
+--------------------------------+
               |
               V
+--------------------------------+
|      Layer : 1                 |
|      Nodes : 100               |
| Inpt Shape : 7,000 x 3,072     |
| Wgts Shape : 3,072 x   100     |
| Outp Shape : 7,000 x   100     |
| Activation : relu              |
+--------------------------------+
               |
               V
+--------------------------------+
|      Layer : 2                 |
|      Nodes : 75                |
| Inpt Shape : 7,000 x   100     |
| Wgts Shape : 100   x    75     |
| Outp Shape : 7,000 x    75     |
| Activation : relu              |
+--------------------------------+
               |
               V
+--------------------------------+
|      Layer : 3                 |
|      Nodes : 50                |
| Inpt Shape : 7,000 x    75     |
| Wgts Shape : 75    x    50     |
| Outp Shape : 7,000 x    50     |
| Activation : relu              |
+--------------------------------+
               |
               V
+--------------------------------+
|      Layer : 4                 |
|      Nodes : 30                |
| Inpt Shape : 7,000 x    50     |
| Wgts Shape : 50    x    30     |
| Outp Shape : 7,000 x    30     |
| Activation : relu              |
+--------------------------------+
               |
               V
+--------------------------------+
|      Layer : 5                 |
|      Nodes : 20                |
| Inpt Shape : 7,000 x    30     |
| Wgts Shape : 30    x    20     |
| Outp Shape : 7,000 x    20     |
| Activation : relu              |
+--------------------------------+
               |
               V
+--------------------------------+
|      Layer : output            |
|      Nodes : 1                 |
| Inpt Shape : 7,000 x    20     |
| Wgts Shape : 20    x     1     |
| Outp Shape : 7,000 x     1     |
| Activation : sigmoid           |
+--------------------------------+

11.计算成本

一旦前向传播部分完成,就有必要测量模型的错误程度。这将用于在反向传播步骤中更新模型参数。

11.1.设置成本函数

第一步是编写用于获取模型成本的函数。最终,第一轮训练的结果有多差并不重要;记住,模型是用随机数初始化的。重要的是,成本函数应该确定网络成本的单个,并且该单个函数将被用于反向传播步骤中使用的导数函数。

请注意,这里使用了一个非常小的ε值(epsi),它有效地调整了模型做出的完美预测。这样做的原因不是我们不希望模型预测一个完美值,而是我们希望模型预测一个完美值的概率。此外,不可能取一个0的对数值,因此有必要将它调整为稍微偏离零点一点。

get_ComputeCost <- function(pred, true, epsi=1e-10) {

    # Get number of samples
    samp <- length(true)

    # Instantiate totals
    total_cost <- 0

    # Loop for each prediction
    for (i in 1:samp) {

        # Adjust for perfect predictions
        if (pred[i]==1) {pred[i] %<>% subtract(epsi)}
        if (pred[i]==0) {pred[i] %<>% add(epsi)}

        # Calculate totals
        total_cost <- total_cost - 
            (
                true[i] * log(pred[i]) 
                + 
                (1-true[i]) * log(1-pred[i])
            )

    }

    # Take an average
    cost <- (1/samp) * total_cost

    # Return
    return(cost)

}

然后,必须将成本应用回网络。为此,将完全相同的值应用于网络的每一层。

set_ApplyCost <- function(network_model, cost) {

    # Apply back to the model
    for (layer in network_model %>% names) {
        network_model[[layer]][["cost"]] <- cost
    }

    # Return
    return(network_model)
}

11.2.运行成本函数

下面的代码块运行成本函数,利用上面定义的函数。

network_model %<>% set_ApplyCost(
    get_ComputeCost(network_model[["output"]][["acti"]], trn_cls)
)

12.反向传播

反向传播函数旨在获取模型的成本,然后区分网络中的每个权重和偏差矩阵,以确定网络中的每个参数对最终成本值的贡献程度。要做到这一点,流程从结束到开始反向工作,遵循以下步骤:

  1. 区分最终成本值
  2. 区分激活矩阵
  3. 线性代数矩阵的微分
  4. 继续上一层。

反向传播过程中的每一步都需要微积分来导出值,最终的函数在这里实现。由于本文并不打算展示如何推导方程,而是更多地展示如何运行函数,因此这里不包括必要的代数步骤。

12.1.区分成本

与用于获取成本的函数类似,首先计算成本差值,然后将其应用于网络的每一层。

get_DifferentiateCost <- function(pred, true) {

    # Do work
    diff_cost <- -(
        divide_by(true, pred) - divide_by(1-true, 1-pred)
    )

    # Return
    return(diff_cost)

}set_ApplyDifferentiateCost <- function(
    network_model, 
    cost_differential
    ) {

    # Do work
    for (layer in names(network_model)) {
        network_model[[layer]][["back_cost"]] <- cost_differential
        if (layer=="output") {
            network_model[[layer]][["back_acti"]] <- network_model %>% 
                extract2(layer) %>% 
                extract2("back_cost") %>% 
                t()
        }
    }

    # Return
    return(network_model)

}

12.2.差异化激活

因为每个激活都有自己的函数,所以也有一个可以用微积分计算的函数的导数。

let_BackwardActivateRelu <- function(diff_acti_curr, linr_curr) {

    # Do work
    diff_linr_curr <- diff_acti_curr
    diff_linr_curr[linr_curr<=0] <- 0

    # Return
    return(diff_linr_curr)

}let_BackwardActivateSigmoid <- function(diff_acti_curr, linr_curr) {

    # Do work
    temp <- 1/(1+exp(-linr_curr))
    diff_linr_curr <- t(diff_acti_curr) * temp * (1-temp)

    # Return
    return(t(diff_linr_curr))

}

12.3.微分线性

到目前为止,已经定义了所有这些,下一步是组合成一个单一的函数,它可以在每层使用一次,运行必要的反向传播微分函数。

注意这里的输出实际上是三个元素的列表。这是Rpython的关键区别。在R中,函数只能输出单个元素;相比之下python能够从每个函数返回多个元素。

get_DifferentiateLinear <- function(
    back_linr_curr,
    acti_prev,
    wgts,
    bias
    ) {

    # get number of samples
    samp <- acti_prev %>% dim %>% extract(2)

    # Differentiate weights
    diff_wgts <- 1/samp * (back_linr_curr %*% acti_prev)

    # Differentiate bias
    diff_bias <- 1/samp * rowSums(back_linr_curr, dims=1)

    # Differentiate activation
    diff_acti_prev <- wgts %*% back_linr_curr

    # Consolidate in to one list
    list_linr <- list(
        diff_acti_prev, 
        diff_wgts, 
        diff_bias
    )

    # Return
    return(list_linr)

}

12.4.反向传播

在定义了微分函数之后,接下来需要将这些单独的函数组合成一个单一的组合函数,该函数可以每层运行一次。

首先,我们将设置函数,其次我们将运行它。

12.4.1。设置反向传播功能

待定义的反向传播函数(set_BackwardProp())必须设计为通过以下步骤运行:

  1. 沿方向穿过每一层。这是必要的,因为从逻辑上讲,反向传播函数需要反向运行。
  2. 提取图层名称。
  3. 跳过input层。同样,这也很符合逻辑,因为这一层位于网络的起点,不需要反向传播。
  4. 提取上一层的名称。
  5. 提取相关矩阵用于后续计算。
  6. 提取该特定层的相关激活函数。
  7. 设置一些空矩阵,这些空矩阵将存放相关的微分矩阵。
  8. 当前层的activation矩阵的微分
  9. 区分linear矩阵,包括:当前层的
    weight矩阵。
    bias当前层的矩阵。
    activation前一层的矩阵。
  10. 将信息应用回network_model中的相关位置。
  11. 返回更新后的network_model
set_BackwardProp <- function(network_model) {

    # Loop through each layer in reverse order
    for (layr_indx in network_model %>% names() %>% length() %>% 1:. %>% rev) {

        # Get the layer name
        layr_curr <- network_model %>% 
            names() %>% 
            extract(layr_indx)

        # Skip the 'input' layer
        if (layr_curr == "input") next

        # Get the previous layer name
        layr_prev <- network_model %>% 
            names %>% 
            extract(layr_indx-1)

        # Set up the existing matrices
        linr_curr <- network_model %>% 
            extract2(layr_curr) %>% 
            extract2("linr")
        wgts_curr <- network_model %>% 
            extract2(layr_curr) %>% 
            extract2("wgts")
        bias_curr <- network_model %>% 
            extract2(layr_curr) %>% 
            extract2("bias")
        acti_prev <- network_model %>% 
            extract2(layr_prev) %>% 
            extract2("acti")
        diff_acti_curr <- network_model %>% 
            extract2(layr_curr) %>% 
            extract2("back_acti")

        # Get the activation function
        acti_func_back <- network_model %>% 
            extract2(layr_curr) %>% 
            extract2("acti_func") %>%
            str_to_title %>% 
            paste0("let_BackwardActivate", .)

        # Set up the empty matrices
        diff_linr_curr <- matrix()
        diff_acti_prev <- matrix()
        diff_wgts_curr <- matrix()
        diff_bias_curr <- matrix()

        # Differentiate activation
        diff_linr_curr <- get(acti_func_back)(
            diff_acti_curr,
            linr_curr
        )

        # Differentiate linear
        list_linr <- get_DifferentiateLinear(
            back_linr_curr=diff_linr_curr,
            acti_prev=acti_prev,
            wgts=wgts_curr,
            bias=bias_curr
        )
        diff_acti_prev <- list_linr %>% extract2(1)
        diff_wgts_curr <- list_linr %>% extract2(2)
        diff_bias_curr <- list_linr %>% extract2(3)

        # Apply back to model
        network_model[[layr_prev]][["back_acti"]] <- diff_acti_prev
        network_model[[layr_curr]][["back_linr"]] <- diff_linr_curr
        network_model[[layr_curr]][["back_wgts"]] <- diff_wgts_curr
        network_model[[layr_curr]][["back_bias"]] <- diff_bias_curr

    }

    # Return
    return(network_model)

}

12.4.2。运行反向传播功能

定义了这个函数之后,下一步是运行这个函数。下面的代码块用tic()toc()函数包装,以确定函数运行需要多少时间。

tic()
network_model %<>% set_BackwardProp()
toc()

它打印:

8.84 sec elapsed

如图所示,运行这个函数大约需要 9 秒钟。考虑到有超过320,000个参数需要更新(参见 检查模型参数 一节),这是相当可观的。

13.更新模型参数

13.1.语境

在模型参数被差分后,在通过重新运行前向传播函数再次重新训练网络之前,需要更新网络的相关参数(权重和偏差)。

用于更新这些参数的方法被称为随机梯度下降。更多信息,参见神经网络中的随机梯度学习反向传播和随机梯度下降法

当然,还有其他实现神经网络优化的方法。文献在这方面已经花费了大量的精力。存在诸如 RMSPropAdam 的算法,这些算法实现了智能方法以实现更快的收敛和更精确的最终结果。这方面的一些好资料来源包括机器学习模型优化器的经验比较神经网络不同优化器的概述。为了进一步增强和优化,探索这些优化选项非常重要。

13.2.过程

尽管如此,实现随机梯度下降的过程实际上非常简单:

  1. 01之间指定一个给定的学习率(通常是很小的数字,例如0.001)。
  2. 取差分后的权重偏差矩阵,在方向上乘以学习率
  3. 将更新的差分权重偏差矩阵相加,并添加到原始的权重偏差矩阵。
  4. 逐层重复这个过程。

13.3.设置更新模型功能

为了设置用于更新模型参数的功能,使用以下步骤:

  1. 指定learning_rate作为函数的参数。
  2. 正向循环通过网络中的每一层。
  3. 提取图层名称。
  4. 跳过input层(因为它不需要更新)。
  5. 定义back_wgtsback_bias矩阵的梯度步骤。
  6. 将梯度步骤应用于原始的wgtsbias矩阵。
  7. 返回更新的模式。
set_UpdateModel <- function(network_model, learning_rate=0.001) {

    # Do work
    for (index in network_model %>% names() %>% length() %>% 1:.) {

        # Get layer name
        layr <- network_model %>% 
            names() %>% 
            extract(index)

        # Skip 'input' layer
        if (layr=="input") next

        # Define gradient steps for the weight
        grad_step_wgts <- -1 * 
            (
                learning_rate * network_model[[layr]][["back_wgts"]]
            ) # Define gradient steps for the bias
        grad_step_bias <- -1 * 
            (
                learning_rate * network_model[[layr]][["back_bias"]]
            )

        # Take steps
        network_model[[layr]][["wgts"]] <- network_model %>% 
            extract2(layr) %>% 
            extract2("wgts") %>% 
            add(t(grad_step_wgts))
        network_model[[layr]][["bias"]] <- network_model %>% 
            extract2(layr) %>% 
            extract2("bias") %>% 
            add(grad_step_bias)

    }

    # Return
    return(network_model)

}

13.4.运行更新模型功能

定义了函数之后,我们运行网络模型。

network_model %<>% set_UpdateModel(0.01)

14.端到端运行模型

现在,是时候把这一切结合起来了。端到端运行模型实质上意味着:

  1. 应用正向传播。
  2. 计算成本。
  3. 运行反向传播。
  4. 更新模型参数。
  5. 重复…

这种重复的每一次被称为一个时期。对于网络来说,很典型的是经过许多时代才被更新。有时数百个,有时数千个纪元;运行的确切历元数由数据科学家根据众多变量自由决定。

这里需要添加一个额外的步骤,那就是批处理数据。为此,我们可以考虑在每个历元内,将数据分批到可等分的组中,并用于后续处理。在这种情况下,模型参数将在每批之后更新。当全部数据都完整地通过模型时,这就被认为是一个时期。

14.1.设置列车模型功能

为了以编程方式说明这一点,编写了以下函数。

注意,这里包含了许多自定义函数(每个函数的源代码可以在这里找到)。这些功能包括:

  • get_Modulus()
  • get_BatchIndexes()
  • get_VerbosityValues()
  • get_TimeDifference()
  • plt_PlotLearningCurve()

该功能的步骤包括:

  1. 开始计时。
  2. 声明将返回哪些信息。
  3. 实例化网络。
  4. 初始化网络。
  5. 循环通过每个时期。
  6. 循环每个批次。
  7. 对该特定批次的数据进行子集划分。
  8. 向前传播。
  9. 计算成本。
  10. 将成本应用于网络。
  11. 区分成本。
  12. 运行反向传播。
  13. 更新模型参数。
  14. 运行下一批。
  15. 在纪元结束时节省总成本。
  16. 打印相关纪元编号处的更新(来自verbosity参数)
  17. 贯穿下一个纪元。
  18. 保存更新的网络。
  19. 打印学习曲线。
  20. 返回输出。
let_TrainModel <- function(
    x_train,y_train,
    input_nodes=dim(x_train)[2], 
    hidden_nodes=c(100, 50, 10), 
    output_nodes=1,
    initialisation_algorithm="xavier",
    initialisation_order="layers",
    activation_hidden="relu", 
    activation_final="sigmoid",
    batches=get_Modulus(dim(x_train)[1])[4], 
    epochs=500, 
    learning_rate=0.001,
    verbosity=NA,
    print_learning_curve=TRUE
    ) {

    # Begin the timer
    time_begin <- Sys.time()

    # Set return values
    output <- list(
        network_model=network_model,
        results=list(
            cost=vector()
            # Can add more, such as accuracy or specificity.
        )
    )

    # Instantiate
    network_model <- set_InstantiateNetwork(
        input=input_nodes,
        hidden=hidden_nodes, 
        output=output_nodes
    )

    # Initialise
    network_model <- set_InitialiseModel(
        network_model=network_model, 
        initialisation_algorithm=initialisation_algorithm, 
        initialisation_order=initialisation_order
    )

    # Loop each epoch
    for (epoch in 1:epochs) {

        # Loop each batch
        for (batch in 1:batches) {

            # Set indices
            batch_indexes <- get_BatchIndexes(
                vector=1:dim(x_train)[1], 
                batches=batches, 
                batch=batch,
                seed=1234
            )

            # Set data
            x_train_batch <- x_train[batch_indexes,]
            y_train_batch <- y_train[batch_indexes]

            # Forward Prop
            network_model <- set_ForwardProp(
                network_model = network_model, 
                data_in = x_train_batch, 
                activation_hidden = activation_hidden, 
                activation_final = activation_final
            )

            # Get cost
            cost <- get_ComputeCost(
                pred = network_model[["output"]][["acti"]], 
                true = y_train_batch, 
                epsi = 1e-10
            )

            # Apply cost
            network_model <- set_ApplyCost(
                network_model = network_model, 
                cost = cost
            )

            # Differentiate cost
            network_model <- set_ApplyDifferentiateCost(
                network_model = network_model, 
                cost_differential = get_DifferentiateCost(network_model[["output"]][["acti"]], y_train_batch)
            )

            # Backprop
            network_model <- set_BackwardProp(network_model)

            # Update parameters
            network_model <- set_UpdateModel(
                network_model = network_model, 
                learning_rate = learning_rate
            )

        }

        # Save cost
        output[["results"]][["cost"]] %<>% c(cost)

        # Print update
        if (!is.na(verbosity)) {
            if (epoch %in% get_VerbosityValues(epochs, verbosity)){
                if (epoch == verbosity) {
                    "Learning rate: {}\n" %>% 
                        str_Format(learning_rate) %>% 
                        cat()
                }
                "Epoch {}, Cost: {}, Time: {}\n" %>% 
                    str_Format(
                        epoch, 
                        round(cost, 5), 
                        get_TimeDifference(time_begin)
                    ) %>% 
                    cat()
            }
        }

    }

    # Re-apply back to the output list
    output[["network_model"]] <- network_model

    # Print the results
    if (print_learning_curve == TRUE) {

        tryCatch(
            expr={
                output %>% 
                    extract2("results") %>% 
                    extract2("cost") %>% 
                    plt_PlotLearningCurve(
                        input_nodes=input_nodes, 
                        hidden_nodes=hidden_nodes, 
                        output_nodes=output_nodes,
                        initialisation_algorithm=
                            initialisation_algorithm, 
                        initialisation_order=initialisation_order,
                        activation_hidden=activation_hidden, 
                        activation_final=activation_final,
                        epochs=epochs, 
                        learning_rate=learning_rate, 
                        verbosity=verbosity,
                        run_time=get_TimeDifference(time_begin)
                    ) %>% 
                    print()
            },
            warning=function(message){
                writeLines("A Warning occurred:")
                writeLines(message)
                return(invisible(NA))
            },
            error=function(message){
                writeLines("An Error occurred:")
                writeLines(message)
                return(invisible(NA))
            },
            finally={
                #Do nothing...
            }
        )

    }

    # Return
    return(output)

}

14.2.运行列车模型功能

已经设置了培训功能,现在让我们运行它。

请注意:

  1. 仅使用训练数据(参见 分割数据 部分)。
  2. 使用 he 初始化算法,阶数为 2。
  3. relu 激活算法用于隐藏层,sigmoid 激活算法用于输出层。
  4. 有 56 个批次,50 个纪元。
  5. 每 10 个时期打印一次模型结果。
  6. 学习曲线打印在最后。
training_output <- let_TrainModel(
    x_train=trn_img, 
    y_train=trn_cls,
    input_nodes=dim(trn_img)[2], 
    hidden_nodes=c(100,75,50,30,20), 
    output_nodes=1,
    initialisation_algorithm="he", 
    initialisation_order=2,
    activation_hidden="relu", 
    activation_final="sigmoid",
    batches=56, epochs=50, 
    learning_rate=0.01,
    verbosity=10, 
    print_learning_curve=TRUE
)

它打印:

Learning rate: 0.01
Epoch 10, Cost: 0.32237, Time: 4.86 mins
Epoch 20, Cost: 0.30458, Time: 9.58 mins
Epoch 30, Cost: 0.28903, Time: 12.23 mins
Epoch 40, Cost: 0.29525, Time: 14.87 mins
Epoch 50, Cost: 0.29884, Time: 17.43 mins

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8 :神经网络学习曲线

有用!这个情节证明了模型是在训练的,随着时间的推移,它在继续学习,提高性能。

请注意,成本线大约从0.4开始,并在 20 个时期后迅速下降到0.3。为了完成全部 50 个纪元,这大约需要12 minutes。这可以看作是一种成功。

14.3.进一步的实验

由于这些功能已经被设置的性质,进一步的实验和优化是非常容易的。

尝试以下方法可能是合理的:

  1. 不同的隐藏层数或每层的节点数(改变hidden_nodes参数),
  2. 不同的初始化算法或初始化顺序(改变initialisation_alghorithminitialisation_order参数),
  3. 隐藏层上的不同激活功能(改变activation_hidden参数),
  4. 每个时期不同的批次数量(更改batches参数),
  5. 不同数量的时期(改变epochs参数),
  6. 不同的学习率(改变learning_rate参数),
  7. 获取更多数据(回想在 下载数据 部分,仅10,000图像被下载;但是还有另一个50,000图像可用于进一步训练)。

这是一个尝试不同参数的实验。注意结果是如何变化的。这下一轮训练花了34 minutes跑。

training_output <- let_TrainModel(
    x_train=trn_img, 
    y_train=trn_cls,
    input_nodes=dim(trn_img)[2],
    hidden_nodes=c(100,75,50,30,20), 
    output_nodes=1,
    initialisation_algorithm="xavier", 
    initialisation_order="layers",
    activation_hidden="relu", 
    activation_final="sigmoid",
    batches=56, epochs=100, 
    learning_rate=0.001,
    verbosity=20, 
    print_learning_curve=TRUE
)

它打印:

Learning rate: 0.001
Epoch 20, Cost: 0.33354, Time: 5.4 mins
Epoch 40, Cost: 0.29891, Time: 10.26 mins
Epoch 60, Cost: 0.30255, Time: 15.2 mins
Epoch 80, Cost: 0.29968, Time: 20.84 mins
Epoch 100, Cost: 0.29655, Time: 26.09 mins

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9 :神经网络的第二条学习曲线

然后,数据科学家的工作是确定这些参数的最佳配置,以实现更好的性能比率。参见机器学习模型中的超参数优化了解更多详情。

然而,由于这篇文章不是为了让达到最佳结果,而是为了让展示如何达到所述结果,以及所涉及的过程/方法。因此,本文不再做进一步的实验。

15.创建预测

训练完网络后,下一步是将它应用于test数据,看看它能多准确地预测未知数据。

15.1.设置预测

第一步是编写两个自定义函数。第一个将接受测试数据和训练模型,并将返回包含预测值和真实值的数据框。然后以一种ggplot风格的输出漂亮地打印出混淆矩阵。

get_Prediction <- function(
    x_test, 
    y_test, 
    network_model, 
    threshold=0.5
    ) {

    # Create prediction
    predic <- set_ForwardProp(
        network_model=network_model, 
        data_in=x_test, 
        activation_hidden="relu", 
        activation_final="sigmoid"
    )

    # Extract probabilities
    probas <- predic %>% 
        extract2("output") %>% 
        extract2("acti")

    # Define results
    result <- data.frame(
        probs=probas,
        truth=y_test
    )

    # Add prdic
    result %<>% 
        mutate(prdic=ifelse(probas>threshold, 1, 0))

    # Return
    return(result)

}plt_ConfusionMatrix <- function(confusion_matrix) { # Do work
    plot <- confusion_matrix %>% 
        extract("table") %>% 
        as.data.frame() %>% 
        rename_all(str_remove_all, "table.") %>% 
        rename("Prediction"=1, "Reference"=2) %>% 
        mutate(
            goodbad = ifelse(
                Prediction == Reference, 
                "good", 
                "bad"
            )
        ) %>%
        group_by(Reference) %>% 
        mutate(prop = Freq/sum(Freq)) %>% 
        ungroup() %>% 
        {
            ggplot(., 
                aes(
                    x = Reference, 
                    y = Prediction, 
                    fill = goodbad, 
                    alpha = prop
                )) +
                geom_tile() +
                geom_text(
                    aes(label = Freq), 
                    vjust = .5, 
                    fontface  = "bold", 
                    alpha = 1
                ) +
                scale_fill_manual(
                    values=c(good="green", bad="red")
                ) +
                scale_x_discrete(
                    limits=levels(.$Reference), 
                    position="top"
                ) +
                scale_y_discrete(
                    limits=rev(levels(.$Prediction))
                ) +
                labs(
                    title="Confusion Matrix",
                    subtitle=paste0(
                        "For: '", 
                        .$Prediction[1], 
                        "' vs '", 
                        .$Prediction[2], 
                        "'"
                    )
                )
        }

    # Return
    return(plot)

}

15.2.运行预测

下一步是实际运行预测。这一步是不言自明的。该函数的参数包括测试数据和训练模型。

# Create prediction
Prediction <- get_Prediction(
    tst_img, 
    tst_cls, 
    training_output[["network_model"]], 
    0.1
)

15.3.视图预测

创建了这个预测之后,接下来需要可视化输出。与 检查图像 部分相同,以下代码块显示了前 16 个图像,并为每个图像返回一个标签。

正如所见,该模型显然能够识别一些,但它也犯了其他错误。

# Define classes
ClassList <- c("0"="Not", "1"="Car")# Set list
lst <- list()# Loop 16 images
for (image in 1:16) {
    lst[[image]] <- plt_PlotImage(
        cifar$images[-partition,,,], 
        Prediction[["prdic"]], 
        ClassList,
        image
    )
}# View images
gridExtra::grid.arrange(grobs=lst, ncol=4)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 10 :预测图像

15.4.检验预测

下一步是对数据进行统计测试,看看它有多准确。为此,使用了confusionMatrix()功能(来自caret包)。根据该输出,可以选择适当的度量,以便进一步优化神经网络。

因为这篇文章仅仅是关于展示这个方法,所以我们不会在这里进行更多的优化。

# Set Up
ConfusionMatrix <- Prediction %>% 
    mutate_at(c("truth","prdic"), ~ifelse(.==1,"car","not")) %>% 
    select(prdic,truth) %>% 
    table %>% 
    caret::confusionMatrix()# Print
ConfusionMatrix %>% print()

它打印:

Confusion Matrix and Statisticstruth
prdic  car  not
  car  188 1118
  not  121 1573

               Accuracy : 0.587              
                 95% CI : (0.569, 0.605)     
    No Information Rate : 0.897              
    P-Value [Acc > NIR] : 1                  

                  Kappa : 0.079              

 Mcnemar's Test P-Value : <0.0000000000000002

            Sensitivity : 0.6084             
            Specificity : 0.5845             
         Pos Pred Value : 0.1440             
         Neg Pred Value : 0.9286             
             Prevalence : 0.1030             
         Detection Rate : 0.0627             
   Detection Prevalence : 0.4353             
      Balanced Accuracy : 0.5965             

       'Positive' Class : car

下一步是绘制混淆矩阵。理解和解释混淆矩阵的一个很好的来源可以在这里找到:混淆矩阵。在这个网站中,它还包括一个图像(也复制在下面),以获得非常好的视觉理解。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11 :混淆矩阵度量

当我们可视化我们的模型的混淆矩阵时,我们可以看到它成功地预测了大多数图像。然而,最大数量的坏分类是当它预测图像是一辆汽车时,而它实际上不是。

# Plot Confusion Matrix
ConfusionMatrix %>% 
    plt_ConfusionMatrix()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 11 :生成混淆矩阵

另一个好的分析和绘图工具是使用 ROC 曲线(接收机工作特性)。一条 ROC 曲线是一个图表,它说明了一个二元分类器系统的诊断能力,因为它的区分阈值是变化的(见接收器操作特性)。

本质上,一个创建完美预测的模型将使曲线完美地“拥抱”该图的左上角。由于我们的模型没有做到这一点,显然进一步的训练和优化是必要的。为此,有必要继续进行 进一步实验 一节中提到的实验。

# Print ROC
Prediction %>% 
    ggplot() +
    plotROC::geom_roc(aes(m=probs, d=truth), n.cuts=0) +
    plotROC::style_roc(
        theme=theme_grey,
        ylab="True Positive Rate",
        xlab="False Positive Rate"
    ) +
    theme(
        plot.title = element_text(hjust=0.5),
        plot.subtitle = element_text(hjust=0.5)
    ) + 
    labs(
        title="ROC Curve",
        y="True Positive Rate",
        x="False Positive Rate"
    )

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 12 : ROC 曲线

16.结论

这是如何在R中构建香草神经网络的有效表示。在这里,我们展示了如何:

  1. 访问并检查数据
  2. 实例化和初始化网络
  3. 向前传播
  4. 计算成本
  5. 反向传播
  6. 更新模型
  7. 建立一个训练方法来循环每一件事
  8. 预测和评估绩效

它还展示了如何完全在R中做到这一点,而不使用任何预定义的深度学习框架。当然,有其他的包可以执行所有的步骤,并且可能以一种计算效率更高的方式。但是这些包的使用不是本文的目标。在这里,目的是消除“黑箱”现象,并展示如何从头开始创建这些深度学习框架。

如图所示,这些前馈神经网络的架构有效地使用了矩阵乘法微分来调整网络的“权重”,并增加其预测能力。它确实需要大量的数据,也确实需要很长时间来训练和优化。此外,还有许多(许多,许多)不同的架构可供选择,所有这些都由应用该方法的数据科学家决定。

说到底,这篇文章已经证明了深度学习绝对可以在R中实现。从而破除了“为了做深度学习,你必须使用Python”的迷思。当然,深度学习在Python有很多成功的实现;比如:用 Python 从零开始构建前馈神经网络。在 Swift 中也有实现相同目标的例子(Swift 中的深度神经网络,经验教训),在 C++中也有实现相同目标的例子(从零开始用 C++实现神经网络),在 Java 中也有实现相同目标的例子(用纯 Java 实现人工神经网络(无外部依赖性))。因此,所使用的具体语言实际上是不相关的。重要的是用例、环境、业务工件以及数据科学家感到舒适的语言。

17.附言

鸣谢:本报告是在他人的帮助下编写的。致谢:
亚历克斯·斯克里文

出版物:本报告同时在以下网站发布:
—RPubs:RPubs/chrimaho/VanillaNeuralNetworksInR
—GitHub:GitHub/chrimaho/VanillaNeuralNetworksInR
—Medium:Medium/chrimaho/VanillaNeuralNetworksInR

变更日志:本出版物在以下日期修改:
—02/11/2020:原始出版日期。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值