TowardsDataScience 博客中文翻译 2020(六百四十一)

原文:TowardsDataScience Blog

协议:CC BY-NC-SA 4.0

模拟疾病的传播

原文:https://towardsdatascience.com/modeling-the-spread-of-diseases-821fc728990f?source=collection_archive---------16-----------------------

SIR 模型的模拟练习

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

约翰·西米托普洛斯在 Unsplash 上的照片

这些天,关于新冠肺炎病毒传播的新闻占据了所有报纸的专栏,所以我尝试了一个关于这个主题的简单建模练习。

在继续之前,我需要说两点。第一点很明显:本文仅供参考,不提供任何医疗建议,也不能替代专业医疗。第二个是,作为我的习惯,我选择使用手绘公式,因为我发现在介质上使用乳胶相当烦人。我提前为书法道歉。

在流行病学中,有一个数学技术家族,称为房室模型,用于传染病的建模。顾名思义,在这些模型中,种群被分成不同的组,并假设在相同区间中的个体具有相同的特征。

我们来考虑一下 SIR 型号,这是最简单的一种。在这个模型中,种群被分成三组:易感个体, **S,**感染个体, I ,以及 R 。应该注意的是,这里的术语“康复的”表示可能已经痊愈并产生免疫力的个体,或者可能已经死亡的个体,但是在任何情况下,他们都不能再被感染。

假设

SIR 模型依赖于几个重要的假设。

  • 首先,没有人加入易感群体,因为我们忽略了出生和移民,我们假设人们只能被感染一次。因此,个体离开易感群体的唯一途径就是被感染。同样,受感染的个体最终只能进入康复组。

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

隔间之间的流动

  • 第二,人口中的所有个体都具有相同的患病概率,并且他们的年龄分布均匀地分布在 0预期寿命 L 之间(这种假设尤其对发达国家是合理的)。
  • 第三,群体的同质混合:个体与群体其余部分的接触也遵循均匀分布,即个体不是更小群体的一部分。由于通常存在的社会结构,这种假设不太合理,但有必要使模型易于处理。

基本定义

在定义模型之前,我们需要引入两个基本的流行病学量:

  • 感染率 β ,即单位时间内单个(被感染)个体感染的人数。这个量与单位时间内的接触次数(假定不变)和疾病传播的概率有关;
  • 痊愈/死亡率𝛾 ,是单位时间内痊愈的感染者的平均比率。例如,如果一次感染的持续时间***【D】***为 10 天,那么从统计数据来看,当前感染人群的 1/10 每天都会康复。

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

恢复速度和疾病持续时间

感染率和恢复率之间的比率给了我们另一个基本的衡量标准,即r₀的基本再生数:

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

基本复制数

基本生殖数代表疾病的生殖比率,是易感个体群体中一个受感染个体直接产生的预期病例数。

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

每个被感染的个体预计会感染另外两个个体

如果估计准确的话,这是一个非常重要的数字,因为它区分了自熄性疾病( R₀ < 1 )、地方性疾病( R₀=1 )和流行病( R₀ > 1 )。

模型

为了开始建模,让我们定义一组变量。

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

模型的变量

给定模型假设,以下等式成立:

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

平衡方程式

为了推广该模型并简化计算,我们将从现在开始考虑每一组相对于总体的分数:

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

三类人口的比例

给定上述假设,这些量应该如何随时间变化?

随着人们感染这种疾病,易感者的数量会减少,所以这种变化肯定是负面的。单个(被感染)个体在单位时间内感染 β 【易感】个体,则单位时间内感染者的总分数等于:

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

单位时间内受感染个体的比例

另一方面,如果一部分𝛾感染的个体在单位时间内康复,则在单位时间内康复的个体的总比例为:

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

单位时间内恢复个体的分数

现在,使用正确的符号,我们可以写出模型的微分方程:

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

模型方程

它们反映了模型的假设:没有人会再次变得易感,易感者会被感染,所有被感染的个体最终会康复(或死亡),没有其他选择。

模拟

*运行模型的时间到了。迄今为止,我一直在努力寻找新冠肺炎病毒的已知参数。根据[1],恢复的中位时间约为 2 周,因此 β=1/14 *。另一个来源【2】估计基本再生数为 R₀=2.28 ,由此我们可以推断出 𝛾=0.16 。最后,尽管有些说法与此相反,但最被接受的假设仍然是,一个人一旦被感染,就会获得对病毒的免疫力。

为了整合模型的方程,我使用了 scipy 的odeint函数。顺便说一句,[3]为编写 SIR 模型提供了一个很好的例子。我假设最初的感染人数等于 0.1%(T21)。

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

新冠肺炎的 SIR 模型

根据这个模型,在没有任何干预来控制传播的情况下,病毒将在大约 180 天内被消灭,挽救不到 20%的人口。

SIR 模型当然是一个非常简单的模型,不适合塑造复杂的动态,尤其是那些涉及大量不同人群的动态。尽管如此,它仍然适用于小规模同质人群,在这些人群中,更容易尊重模型背后的假设(例如,疫情发生在游轮钻石公主号上)。

当局下令采取的措施如何遏制病毒的传播?最近在一些欧洲地区实施的隔离和检疫措施显然是为了尽可能地限制感染的可能性。在我们的模型中,这反映在因子 β的突然降低。

假设这些措施:

  • 使新的ββ₂=β/3(也许没有这么大的减少),并且
  • 在第 50 天施用,即在感染人数达到高峰之前,

会出现以下情况:

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

减少模型的 β

事实上,通过迅速反应,大幅降低β因子,感染人数立即减少,从而趋近于零。

参考

[1]https://ourworldindata.org/coronavirus

[2]http://www . cid rap . umn . edu/news-perspective/2020/02/study-72000-新冠肺炎-病人-发现-23-死亡率

[3]https://scipython . com/book/chapter-8-scipy/additional-examples/the-sir-epidemic-model/

贝叶斯网络建模

原文:https://towardsdatascience.com/modeling-with-bayesian-networks-c7ebf28a8b6b?source=collection_archive---------17-----------------------

从医学诊断建模导论

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

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

我感觉不舒服。发烧。咳嗽。鼻塞。现在是冬天。我得了流感吗?很有可能。另外,我肌肉疼痛。更有可能。

贝叶斯网络非常适合这种类型的推理。我们有变量,有些变量的值是固定的。我们感兴趣的是给定这些固定值的一些自由变量的概率。

在我们的例子中,我们希望知道我们患流感的概率,给出我们观察到的一些症状,以及我们所处的季节。

到目前为止,它看起来像是用条件概率进行推理。还有更多的吗?是的。多得多。让我们放大这个例子,它就会出来。

走向大规模贝叶斯网络

想象一下,我们的网络模拟了每一种可能的症状,每一种可能的疾病,每一种可能的医学测试的结果,以及每一种可能影响某种疾病概率的外部因素。外部因素可以分为行为因素(吸烟、整天躺在沙发上看电视、吃得太多)、生理因素(体重、性别、年龄)和其他因素。为了更好的衡量,让我们也加入治疗。和副作用。

到目前为止,已经有足够的有用的医学知识来捕捉成千上万的变量(至少)及其相互作用。对于任何一组症状,加上一些行为、生理和其他外部因素的值,我们可以估计各种疾病的概率。还有更多。对于给定的疾病,我们可以要求它给出最可能的症状。还有更多。例如我咳嗽、发高烧,但被诊断为流感,还可能有什么其他疾病?对于给定的诊断,我们的特定症状,以及可能的其他因素,如我们的性别和年龄,我们可以要求它推荐治疗方法。

现在我们有所进展了。这些魔法是如何运作的?这就是我们在这里要探讨的。

连通性

第一个问题,从何而来?来模拟成千上万个变量之间的相互作用。

建模这么多变量之间所有可能的相互作用几乎是不可能的。正是网络给了我们一种机制来穿越这种复杂性。通过让我们指定要建模的交互。目的是寻求一种足够丰富的模式。但不要过于复杂。

说到交互,我们如何决定对哪些进行建模?通常通过领域知识。在我们的案例中,利用了数千年临床实践和研究中获得的医学领域的集体知识。

我们的贝叶斯网会是什么样子?在结构上,一个巨大的有向图,有各种症状、疾病、医学测试、行为因素、生理因素和治疗方案的节点。用适当选择的(或推断的)弧线来模拟它们之间的重要相互作用。例如特定的症状和特定的疾病。

连通性细化

贝叶斯网络在结构上是一个有向图,一个无环图。有向意味着边缘有一个方向,这就是为什么它们被称为非循环表示没有定向循环。这里举一个有向循环的例子:ABCA

除了无环性约束之外,建模者可以完全控制哪些节点与弧线连接以及如何定向它们。也就是说,在复杂的真实世界用例中,比如我们在这里讨论的(医疗诊断),有一个吸引人的指导原则。

选择弧线来模拟直接原因。将它们导向因果关系的方向

所以如果 AB 的直接原因,我们就加弧 AB 。这样的网络被称为因果贝叶斯网络。

因果网络的结构只和它的变量以及因果关系的保真度一样精确。例如,事实可能是 A 导致 B 并且 B 导致 C 。但是我们可能甚至不知道 B 的存在。所以我们能做的最好的事情就是通过弧线 AC 来建模。

因果建模

好吧,让我们从医学角度来考虑因果关系。这是我们想出来的。

**Variable Type A causes Variable Type B        Example**disease         causes symptom            flu causes you to coughbehavior        causes disease            smoking causes lung cancerphysiological   causes disease            aging “causes” various    
factor                                    diseasestreatment       "causes" disease          chemotherapy reduces 
                                          cancertreatment       causes side-effect        chemotherapy causes 
                                          hair-loss

在结束这一部分之前,让我们注意一下,我们不应该太担心弄错一些因果弧。(当然,我们宁愿不要。)后果并不严重。事实上,我们很可能会在网络中发现一个新的非因果弧。对因果关系不明确或不存在的相关性进行建模。事实上,网络甚至分不清随意弧和非随意弧。在我们的用例中没有。

举这个例子。说 AB 强相关。假设你认为 A 导致 B ,那么用圆弧 AB 对此建模。但是你错了。添加这个弧线仍然是一件好事,因为它模拟了相关性。下一节将更详细地讨论非因果弧。

非因果弧

因果关系是网络设计中一个令人信服的指导原则。然而,这还不够。也就是说,添加非因果弧可以进一步改进模型。

考虑变量之间的相关性。例如在一组症状或一组疾病中。集合内的因果关系可能是未知的,甚至是不存在的。不过,我们确实想对相关性进行建模。所以要加上合适的“非因果”弧。

这里有一个简单的例子。说有强有力的证据证明干呕 h 和咽喉痛相关。假设这是网络中仅有的两个变量。在任一方向上用弧线将它们连接起来将捕捉到这种相关性。不考虑弧线会将它们视为独立的。我们不想那样。

网络的主方程

在某些时候,就像一幅画可以展现一个远景一样,数学也可以。我们正处于这一阶段。所以现在开始。

形式上,贝叶斯网络是在 n 个节点上的有向无环图。这些节点,称之为 X 1、 X 2、…、 X n,模拟随机变量。弧线模拟了它们之间的相互作用。

更准确地说,网络的结构将 n 个变量的联合分布分解为

P (X1, X 2,…,Xn)= product _IP(XI |双亲 (Xi))

这里有很多东西需要打开。先说:parents( X i)是有弧线进入 X i 的节点集合,嗯?

让我们用简单的例子慢慢来。都有相同的 5 个节点 A,B,C,D,e。

我们的第一个网络将没有弧线。所以没有一个节点有父节点。因此

P(A,B,C,D,E) = P(A)P(B)P©P(D)P(E)

我们的第二个网络将是一个马尔可夫链。在结构上,图是一条单路 A → B → C → D → E。节点 A 没有任何父节点。节点 B 的父节点是 a,节点 C 的父节点是 B,等等。因此

P(A,B,C,D,E) = P(A)P(B|A)P(C|B)P(D|C)P(E|D)

我们的第三个网络是朴素贝叶斯分类器,其中 E 作为类变量,A、B、C 和 D 作为预测变量。它的图形结构是

E → A,E → B,E → C,E → D

e 没有父母。A、B、C 和 D 中的每一个都有一个双亲:e。因此

P(A,B,C,D,E) = P(A|E)P(B|E)P(C|E)P(D|E)P(E

熟悉朴素贝叶斯分类器的读者会认识到这个等式右边的形式。把 A,B,C,D 看作预测值,E 看作类别变量。

现在我们已经准备好了一个临床例子。

临床网络示例:流感及其症状

考虑变量为流感发烧咳嗽鼻塞季节的网络。为简单起见,假设前四个是布尔型(是/否),第三个是分类型(春、夏、秋、冬)。

因果建模将产生以下弧线:

flu → fever, flu → cough, flu → stuffy nose

让我们给它们加上弧线fluseason。这不是一个因果弧,也就是说,我们可以翻转它的方向。但我们不会。因此它的方向与从流感发出的因果弧线方向一致。这将有助于下一节中的诊断。

有趣的是,这不是巧合,这个网络的结构是朴素贝叶斯分类器。

诊断:从症状到流感

我们想要我们患流感的概率,假设我们有发烧咳嗽鼻塞冬季。让我们把它正式表达为

P(flu = *yes* | fever = *yes*, cough = *yes*, stuffy nose = *yes*, *season* = *winter*)

或者更简明地(更笼统地)表述为

P(flu|fever,cough,stuffy nose, season)

为了推断这一点,我们只需应用贝叶斯法则:

numerator(x) = P(fever|flu=x)*P(cough|flu=x)*P(stuffy nose|flu=x)*P(season | flu=x)*P(flu=x)P(flu=yes|fever, cough, stuffy nose, season) = numerator(yes)/(numerator(yes)+numerator(no))

这就是为什么这个网络被称为贝叶斯网络。从症状到疾病的推断包括贝叶斯推理。

“超越流感”网络

我们已经有一个处方,所以让我们执行。首先,开始添加额外疾病和症状的节点。第二,为行为、生理因素、医学测试等添加节点。第三,按照前面给出的指导,开始添加更多的因果弧。诸如

smoking → lung cancer, aging → disease-1, aging → disease-2, …, aging → disease-*k* chemotherapy → cancer, chemotherapy → hair-loss

接下来,开始添加合适的非因果弧。捕捉症状之间的相关性、疾病之间的相关性等。

这种网络“主干”的宏观结构如下。

behaviors, physiological factors ⇒ diseases 
treatments ⇒ diseases
diseases ⇒ symptoms 
treatments ⇒ side-effects

测试?

复数形式的术语表示某些类型的节点集。比如疾病。X Y 表示从 X 到 Y 的一组圆弧。该级别不显示特定圆弧的首尾。

我们已经讨论过为什么弧集是这样定向的。我们之所以选择行为和生理因素来共同影响疾病,是因为这两类因素相互作用。例如,某些不良行为选择对某些疾病的不利影响在老年人中往往比在年轻人中更大。

事实上,疾病的宏观父母可以更加详细。诸如

behaviors, physiological factors, treatments ⇒ diseases

这将对所有三种类型的因素、行为生理因素疾病治疗的联合相互作用进行建模。也就是说,这种宏观层面的互动通常会产生一个相当复杂的网络。因此,为了传达主干的本质,我们将坚持我们早先的宏观结构。也就是说,例外情况,即影响特定疾病的特定三元组(行为生理因素治疗)总是可以添加进来的。宏观结构只是一个大的图景,而不是一个可执行的模式。该模式仅处于精细级别,由网络的弧线指定。

注意我们有一组节点, tests ,它是悬空的。我们将让您思考如何将这台电视机连接到网络的其余部分。我们应该做检查**疾病,还是疾病**检查,还是其他什么?

培训“超越流感”网络

训练意味着从数据、信念或组合中估计模型的各种概率分布P(XI |parents(Xi))。

训练症状分布

让我们从学习任何一个以其父母为条件的症状的概率分布开始。让我们做一个简化的假设,一个症状的母体只能是疾病。例如,症状咳嗽的父母会包括流感支气管炎

给定一个症状 S 及其父代 pa ( S ),条件概率表捕捉到P(S|pa(S))在 pa ( S )中的疾病数为指数。这是因为原则上 pa ( S )中 n 疾病的任何子集都可能发生。(我们所说的“发生”是指在特定的就诊中诊断出。)还有 2^n 这样的子集。当 n 较大时,这可能相当大。

三个因素将共同缓解这一问题。一个是,大多数症状不会有大量的父母,即大量的疾病可以导致他们。

第二,在任何情况下,被诊断的疾病将是父母的稀疏子集。诊断实例对应于拍摄显示症状的特定人的疾病状态的快照。在所有可能出现这种症状的潜在疾病中,一个人几乎肯定最多只能被诊断出几种。如果不止一个。这种稀疏性对训练有很大帮助。简单来说,稀疏意味着“没有显著的高阶相互作用”。下面的一个数值例子将说明这一现象。

第三个因素是,我们对我们认为包含在给定症状 S 的父母 pa ( S )集合中的内容有一些控制。如果症状的父集变得特别大,我们可以删除与症状不太相关的疾病。

从数据中发现症状的父母

我们应该将哪些疾病设置为给定症状 S 的父母?以前我们建议,作为一个通用的指导方针,使用领域知识。在我们的特殊情况下,有一个更好的方法。患者记录将揭示哪些症状与哪些疾病相关。因此,这方面的结构也可以从数据中有所收获。患者病历记录了许多专家在不同情况下做出诊断的集体智慧。

从数据中了解症状的父母的好处是巨大的。这避免了网络设计人员必须获取领域知识来完成这项工作——无论是通过与领域专家的讨论、扩展阅读还是一些更复杂的机制。即使这项工作被分配给一个大型的建模师和领域专家团队,这样的手工设计也是费力且容易出错的。症状太多,疾病太多。

也就是说,领域知识仍然可以帮助填补病人记录中可能没有涵盖的情况的空白,或者消除信念和数据之间的不一致。简单来说,领域知识+数据驱动学习一般比单独使用任何一种都要好。

我们将在下一节详细讨论患者就诊记录,因为我们无论如何都需要它们来学习网络的参数,例如在P(S|pa(S))中的概率。不管我们如何得出 pa ( S )的结构。

患者就诊记录

我们假设与医学专家的每一次互动都会生成一个新记录,记录观察到的症状和诊断出的疾病。如果诊断出多种疾病,那么观察到的哪些症状与哪种疾病有关也将被捕获。医学专家认为。诊断可以是确定的,也可以是专家认为合适的推测。我们所关心的是这是由专业人士做的。

让我们看一个病人就诊记录的例子。编造的。不是医学建议!

(*symptoms* = high fever, cough, sore throat, lump in throat; *disease* = flu)
(*symptoms* = lump in throat, chest pain; *disease* = gerd)

在这次访问中,确诊了两种疾病:流感GERD 。健康专家暗示两者都有咽喉肿块。

从这样的记录中,我们可以得到以症状为中心的表征,每个表征对应一个观察到的症状。这种表示列出了就诊期间与该症状相关的诊断疾病。这些疾病在就诊记录中也被称为症状的父母。

在我们上面的例子中,记录中喉头肿块的父母是流感GERD

以症状为中心的表征有助于学习症状分布。

发现症状的父母

从我们可以访问的所有患者就诊记录中收集以症状为中心的表征,我们可以很容易地确定症状的父母。这些都是这个数据所涉及的疾病。如果我们只能从单个病人就诊记录中学习的话,喉咙哽咽的父母应该是流感GERD

对于某些症状,大量不同的患者就诊记录可能会产生大量的父母。如前所述,我们可以通过删除与症状不太相关的父项来删减如此大的集合。

根据患者就诊记录训练症状分布

我们想知道,对于每一个症状,它的分布取决于它的父母。我们有一个以症状为中心的数据集可用于这项学习。(如前所述,这来源于患者就诊记录。)

考虑这个数据集中的任何一个实例。它列出了一个症状,以及在患者就诊期间与之相关的疾病。它没有列出症状父母中与没有牵连的疾病。正如我们将在下面看到的,我们也需要这些信息。幸运的是,我们可以通过从症状的父母中减去牵连疾病来推断这些疾病。

让我们看一个例子。说咳嗽的父母是流感肺炎哮喘。(在真实的网络中,这个列表将包括更多的疾病。)比如说咳嗽的父母在某个病历里是流感。由此,我们可以推断,在这种情况下咳嗽不是由肺炎哮喘引起的。虽然在这种情况下,这种推断不是 100%正确的,但重复出现这种相同的推断确实给出了相关条件概率的良好估计。

从这两条信息——症状父母中的哪些疾病与特定患者记录有关,哪些不在特定患者记录中——我们将导出以下形式的训练向量。

cough flu pneumonia asthma
  1    1      0       0

这很容易读懂。它说,在这个患者记录中,咳嗽是存在的,并且在咳嗽的父母中,流感被诊断,肺炎未被诊断,哮喘未被诊断。

接下来,考虑一个患者记录,其观察到的症状列表不包括咳嗽。接下来,根据这组父母中的疾病是否在该记录中被诊断,导出该记录中咳嗽的父母的值。

这里有一个例子。假设患者记录导致了诊断

(symptoms = shortness of breath, chest pain, wheezing; diseases = asthma)

由此,我们可以得出记录

cough flu pneumonia asthma
  0    0      0       1

有了足够丰富的此类记录,当然随着人们在可预见的未来不断生病,这些记录还会不断增加,我们可以了解到 P ( 咳嗽 | 父母 ( 咳嗽)。更广泛地说,任何症状的分布取决于其父母。

单独来看,这样的训练实例是完美的吗?不。在诊断中没有疾病并不意味着现在或不久就没有。这同样适用于症状。也就是说,在足够多样化的环境下,经过大量的训练,这种噪音应该会被信号淹没。例如,如果诊断流感的记录中只有 30%也显示咳嗽是一种观察到的症状,我们可以很有把握地推断流感产生咳嗽作为观察到的症状的时间不超过一半。

训练行为和生理因素对疾病的影响

这里我们提炼宏观结构

behaviors, physiological factors ⇒ diseases

我们将假设所需的信息也可能来自患者记录。

我们试图为每种疾病 D 估计以其双亲为条件的 D 分布的参数。 D 的父母是行为和生理因素的合适子集。哪些行为,哪些生理因素?这些可以通过领域知识来设置,因为关于哪些行为会影响哪些疾病已经知道了很多。(不利地或有利地。)同样对于生理因素。可选地或附加地,疾病的双亲也可以从数据中推断。

让我们从数据上来说明这样的训练。考虑以下患者记录

smoker, 50 years old, male, diagnosed: lung cancer

首先,从这些记录的集合中,我们可以推断肺癌的父母,即影响其诊断的行为和生理因素。与症状分布一样,我们需要另外两种类型的信息来估计肺癌在其父母已知的情况下的分布。

  1. 肺癌的特定诊断中,父母哪一方失踪了?
  2. 如何估计一个人在部分父母在场的情况下不患肺癌的概率?

对于 1,与症状情况一样,缺失的父母是完整的父母集减去该患者记录中的父母集。对于 2,也是在症状的情况下,我们从患者记录中得到这些,其中一些父母患有肺癌,而患者被诊断为没有肺癌。一个例子是没有肺癌的吸烟者。我们如何决定一个因素是否“关键”?试试领域知识。

训练治疗对疾病的影响

我们这里有个问题。我们的宏观结构模式

behaviors, physiological factors ⇒ diseases 
treatments                       ⇒ diseases

也就是说,任何单一疾病 D 都会有两组亲本,一组涉及行为和生理因素的某些组合,另一组涉及治疗。当然,我们可以将这两组父母合二为一。广泛地这样做有前面讨论过的问题。也就是说,在特定疾病的背景下,行为、生理因素和治疗的特定三元组可能值得包括在内。(如前所述。)

总而言之,我们不想崩溃

behaviors, physiological factors ⇒ diseases 
treatments                       ⇒ diseases

到…里面

behaviors, physiological factors, treatments ⇒ diseases

一般来说。

让两组父母分开

那么,对于一种给定的疾病,我们如何将两组父母分开 D ?一种方法是为 D (我们称之为 DI )引入一个额外的变量,如下。

behaviors, physiological factors ⇒ *DI* treatments, DI                   ⇒ D

我们可以认为 DI 是对疾病发作的建模,D 是对疾病在一次或多次治疗后的下一个状态的建模。也就是说,这种方案无法模拟疾病对治疗的动态演变。这将要求 DDI 的父代,这将违反贝叶斯网络上的无环性约束。

让我们看一个具体的例子。

diet, age, gender               → heart disease-I
heart-disease-I, treatment      → heart disease

治疗和副作用

让我们从简单的开始。每个副作用都有一个节点。我们对每种治疗都有一个节点。一个副作用的父母都是有那个副作用的治疗。

让我们看一个例子。

chemotherapy, bone marrow transplantation, …, → fatigue

在我们的网络中包含这样的弧有什么价值?一个是它让我们寻找对特定疾病既有效又有相对轻微副作用的治疗方法。

在这个标度网络中的推论

让我们从重复我们网络的宏观结构开始。这有助于了解网络适合什么类型的推理。

behaviors, physiological factors ⇒ diseases 
treatments                       ⇒ diseases
diseases                         ⇒ symptoms 
treatments                       ⇒ side-effects
tests ?

现在来看具体的推论。每一个后面都有一个关于它如何工作的解释。在这个解释中,我们关注的是是否以及如何从数据或领域知识中计算出各种概率。目的是深入了解网络结构如何简化各种计算。

在实践中,人们可能将推理算法用作黑盒,它将在幕后做任何事情。

如果我吸烟,我是女性,我 75 岁了,患肺癌的可能性有多大?

我们求 P ( 肺癌 | 抽烟75 岁)。

好消息是,这个推论所依据的所有观察结果都是肺癌患者的父母。

坏消息是,肺癌可能有额外的父母。这些需要被边缘化。边缘化包括平均这些额外的父母可以采取的各种价值,加权的概率。由于这些价值观的数量与新增父母的数量成指数关系,边缘化是一个缓慢的过程。确实存在复杂的算法来加速它。他们的讨论超出了本文的范围。

节点分布的常用限制可以缓存在节点上。可以认为这是附加到节点 S 上的,不仅是 P ( S | 父节点 ( S ))还有 P ( S | 子集 ( 父节点 ( S ))用于合适的父节点(S)然后可以适当地使用这种缓存的分发,减少即时边缘化的需要。

本人吸烟,女,75 岁。我一直咳嗽。我有肺癌的可能性是多少?

我们求 P ( 肺癌 | 抽烟75 岁持续咳嗽)。根据贝叶斯法则,

P(lung cancer | smokes, female, 75 years old, persistent cough) =
P(smokes, female, 75 years old, persistent cough | lung cancer)*
**P(lung cancer)**/
**P(smokes, female, 75 years old, persistent cough)**

(稍后我们将解释粗体字体。)

接下来,我们利用一个重要的属性。

给定其父节点,节点有条件地独立于其非后代节点。

由于这是我们第一次在本帖中看到这个属性,让我们深入研究一下。考虑网络ABC。(一个马尔可夫链。)应用前述的条件独立概率,我们得到给定 B 的情况下 C 独立于 A 。即P(C|BA )等于P(C|B)。或者换句话说,一旦我们观察到了B,A的值不会为预测 C 的值提供额外的信息。

将这种条件独立性应用于我们的情况给出

P(smokes, female, 75 years old, persistent cough | lung cancer) =
**P(smokes,female,75 years old|lung cancer)***
**P(persistent cough|lung cancer)**

好,现在让我们收集所有粗体的术语。这些都是有待估计的。我们在下面复制了它们。

**P(lung cancer)
P(smokes, female, 75 years old, persistent cough)
P(smokes,female,75 years old|lung cancer)
P(persistent cough|lung cancer)**

从一组足够丰富的患者记录中很容易估计出 P。一些有用的估计可能已经存在于公共领域。

P ( 持续咳嗽 | 肺癌)也可以从患者记录中估计,作为诊断为肺癌的记录中具有持续咳嗽作为观察症状的部分。

为了估算 P ( 抽烟75 岁持续咳嗽),我们就调用独立性假设。这就给我们留下了 P ( 抽烟)P(年龄)P(持续咳嗽)P()。前三个很容易从数据结合知识来估计。最后一个我们可以设置为 0.5。

稍微跑题一下,严格来说,上一段提到的变量并不都是完全独立的。例如,女性比男性长寿,所以年龄和性别至少是轻度相关的。

最后只剩下 P ( 抽烟75 岁 | 肺癌)。调理(抽烟75 岁)对肺癌使得前三者有条件依赖。因此,我们应该尽可能避免援引独立性。如果我们不能,那也不是世界末日。由此产生的推论仍然是有意义的解释。具体来说,它作为一个朴素贝叶斯分类器运行,根据作为条件独立结果治疗的吸烟女性年龄持续咳嗽来预测肺癌

宏课

从上述例子中得到的宏观教训是,当试图从一些观察到的生理因素和一些观察到的症状来诊断疾病时,生理因素可以被合理地假设为与给定疾病的症状无关。当然,老年人可能比年轻人更容易表现出某些症状。然而,当我们以某种疾病作为解释症状的附加条件时,相比之下,变老的额外影响是很小的。

什么癌症疗法副作用最小?

让我们用逻辑和概率的混合来表达这一点。我们寻求这样的治疗方法:P ( 癌症 | T )高,而对于每一种副作用 SEP(SE|T)低。这里的关键观察是,在两种概率中,作为条件的变量是我们试图计算其概率分布的变量的父变量。(在前面的句子中,如果“变量”一词引起了混淆,请将其替换为“事件”。)因此,我们可以利用网络的结构来高效地计算我们想要的东西。

延伸阅读

https://www . science direct . com/science/article/pii/s 1532046418302041

【https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5519723/】在本文中,疾病和症状提及也是从护士笔记等非结构化文本中提取的。为此,命名实体识别(NER)技术非常有用。(在这种情况下,命名实体是疾病和症状。)查看https://towards data science . com/named-entity-recognition-in-NLP-be 09139 fa 7b 8了解更多关于 NER 的信息。

http://www . cs . CMU . edu/~guest rin/Class/10701-S05/slides/bns-inference . pdf此处为精辟示例

flu, allergy → sinus, sinus → headache, sinus → nose

请将此理解为“流感或过敏导致鼻窦,鼻窦导致头痛,鼻窦会妨碍鼻子的正常功能”。

用 Python 建模你的股票投资组合表现

原文:https://towardsdatascience.com/modeling-your-stock-portfolio-performance-with-python-fbba4ef2ef11?source=collection_archive---------5-----------------------

用 200 行代码评估你的交易表现

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

照片由杰米街Unsplash

当我刚开始学习 Python 时,我的目标之一是理解如何更好地评估我的股票投资组合的财务表现。我看到了凯文·博勒(Kevin Boller)的一篇很棒的文章(T5 ),他用 python 将投资组合与标准普尔 500 指数进行了比较。我强烈建议花几分钟时间通读它,以便更好地理解基本的金融概念,因为我在这里不会深入探讨。

然而,我想要的是数据的时间序列显示,而这更多的是一个时间点的汇总。最初的代码还假设股票销售永远不会发生,我希望它反映在动态时间框架内买入/卖出头寸的现实。

本文的其余部分将探讨我如何构建代码的过程,在时间加权的基础上准确计算性能的挑战,以及如何显示最终结果。下面解释的文件和代码可以在这里找到并分叉

构建问题

我们的目标是从某人的投资组合中读取一个包含“买入”和“卖出”的 CSV 文件,然后计算任意指定时间范围内相对于某个指数的回报率。“听起来不可怕!”——在开始编码之前,我脑海中傲慢的声音说道。事实证明,语音是一个绝望的乐观主义者,实际上有很多障碍,包括:

  • 对于相对的开始和结束日期,如何计算开始时的“当前”持有量?
  • 在给定的时间范围内,你如何计算同一仓位的多次买入和卖出?
  • 当同一支股票在不同的成本基础上进行多次购买/销售时,如何准确描述成本基础?

为了更好地说明这些挑战,这里有一张图,试图将一个场景可视化:

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

显示买卖后正确计算的成本/份额的示例

请看插图,最上面的表格代表投资组合中的一些虚假的买入/卖出数据。下面的部分显示了所选时间段内不同时间点的每日快照。正如你所看到的,在开始日期之前的交易在计算时间段开始时的活跃余额时变得非常重要。仅仅因为我在开始日期之前购买了 100 股苹果股票,并不意味着它应该被排除在外。

你还会注意到,在表格的底部中间,卖出 AAPL 股票时,每股成本变成了 11.53 美元,这是为什么呢?因为 AAPL 股票是在两天后以不同的价格购买的,我们需要合计并平均成本。这意味着你销售的顺序变得很重要!对于本文,我们将假设一切都符合 FIFO 标准,这意味着最早买入的股票是最先卖出的股票。最后,我们看到购买 MSFT 股票将被排除,因为它存在于时间范围之外,所以我们需要在我们的计划中考虑这一点。

解决方案提案

在考虑了上面提到的挑战后,我决定创建一个新的持股和股票价格的“每日”计算,这对于生成准确的分析是必要的。作为一个额外的好处,凯文文章中提到的一切也是可行的,因为几乎所有的计算都是基于拍摄一张快照并将其与后来的快照进行比较。记住这一切,我们将采用以下方法来构建它:

  • 读取包含所有买入/卖出交易日期的投资组合文件,然后在指定的结束日期之前提取所有报价机的每日数据(记住我们需要担心开始日期之前的买入/卖出)
  • 根据提供的起始日期,计算“活跃”投资组合持有量和调整后的单位持有成本
  • 在我的投资组合中创建一个每日“活跃持有”清单,以及股票的每日收盘价
  • 基于每天的合并数据创建计算

现在我们已经有了一个我们想要完成的松散结构,让我们开始打字吧!

Doggo 正在做一个蛇 boi 程序

步骤 1 —获取数据

我们需要做的第一件事是获取我们投资组合中股票的每日数据,以及我们评估投资组合的基准数据。股票数据通常不会以开源或免费的形式提供,尽管有很多令人敬畏的服务,如用于核心分析的 QuandlIEXFinance ,它们对我的普锐斯需求来说是一辆法拉利。幸运的是,有一个名为 yfinance 的优秀库,它可以抓取雅虎财经的股票数据,并以结构化的形式返回。因此,让我们从导入我们需要的库和获取数据的前几个函数开始:

我们正在编写的第一个函数叫做create_market_cal,它使用pandas _ market _ calendars库来查找指定时间段内的所有相关交易日。这个库根据市场自动过滤掉非交易日,所以我不需要担心通过使用类似 pandas.date_range 的东西将数据连接到无效日期。由于我的股票都在美国,我将选择NYSE作为我的日历,然后标准化时间戳,使它们便于以后加入。

get_data函数获取一组股票报价器以及开始和结束日期,然后使用上面列出的 yfinance 库获取数据。您会注意到结束日期参数包括一个timedelta转移,这是因为 yfinance 不包括您提供的结束日期。由于我们不想在设置参数时记住这个警告,我们将使用 timedelta 将这里的日期移动+1。

最后,get_benchmark函数只是馈入get_data,然后放下股票代码。现在我们已经有了初始函数,让我们运行下面的程序来给变量赋值:

portfolio_df = pd.read_csv('stock_transactions.csv')
portfolio_df['Open date'] = pd.to_datetime(portfolio_df['Open date'])symbols = portfolio_df.Symbol.unique()
stocks_start = datetime.datetime(2017, 9, 1)
stocks_end = datetime.datetime(2020, 4, 7)daily_adj_close = get_data(symbols, stocks_start, stocks_end)
daily_adj_close = daily_adj_close[['Close']].reset_index()
daily_benchmark = get_benchmark(['SPY'], stocks_start, stocks_end)
daily_benchmark = daily_benchmark[['Date', 'Close']]
market_cal = create_market_cal(stocks_start, stocks_end)

作为参考,我的 CSV 文件包含以下各列,如果您尝试复制,您需要确保您的 CSV 包含相同的列:

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

事务 CSV 格式

我们现在有四个重要的数据集来继续:

  1. portfolio_df根据我们的购买/销售交易历史
  2. daily_adj_close在指定的结束日期之前,每天关闭我们库存中的所有票据
  3. daily_benchmark与每日收盘数据进行基准比较
  4. market_cal包含我们时间段内市场开放的日期

利用这一点,我们可以前进到下一步,走向辉煌!

第 2 步——找到我们最初的活跃投资组合

现在我们有了这四个数据集,我们需要计算出在指定的起始日期我们积极持有了多少股票。为此,我们将创建两个函数,portfolio_start_balanceposition_adjust

将输出分配给一个变量应该会给你投资组合中的活跃头寸:

active_portfolio = portfolio_start_balance(portfolio_df, stocks_start)

现在我们可以看到代码了,让我们看看内部的工作原理,用通俗的语言展示一下发生了什么。

portfolio_start_balance

首先,我们向portfolio_start_balance函数提供 CSV 数据和开始日期,并创建在开始日期之前发生的所有交易的数据框架。然后,我们将检查在 start _ date 之后是否有未来销售,因为我们最终将重建该数据帧的快照:

positions_before_start = portfolio[portfolio['Open date'] <= start_date]
future_sales = portfolio[(portfolio['Open date'] >= start_date) & (portfolio['Type'] == 'Sell.FIFO')]

然后,我们将创建一个在 start_date 之前发生的销售数据框架。我们需要确保在指定的 start_date,这些都从我们的活动投资组合中剔除:

sales = positions_before_start[positions_before_start['Type'] == 'Sell.FIFO'].groupby(['Symbol'])['Qty'].sum()
sales = sales.reset_index()

接下来,我们将制作在指定时间段内没有任何销售发生的头寸的最终数据框架:

positions_no_change = positions_before_start[~positions_before_start['Symbol'].isin(sales['Symbol'].unique())]

现在,我们将遍历我们的sales数据帧中的每一笔销售,调用我们的position_adjust函数,然后将它的输出追加到我们的空adj_postitions_df中:

adj_positions_df = pd.DataFrame()
for sale in sales.iterrows():
    adj_positions = position_adjust(positions_before_start, sale)
    adj_positions_df = adj_positions_df.append(adj_positions)

现在让我们看看position_adjust函数是如何工作的,这样我们就可以完全理解这里发生了什么。

position_adjust

首先,我们将创建一个名为stocks_with_sales的空数据框架,稍后我们将在其中添加调整后的头寸,另一个数据框架包含所有标记为“买入”的交易。

请记住,我们已经在portfolio_start_balance函数中过滤了“未来购买”,因此无需在此重复。你还会注意到我们是按“开仓日期”排序的,这很重要,因为我们想用 FIFO 方法减去仓位。通过对日期进行排序,我们知道我们可以迭代地遍历从旧到新的位置列表:

stocks_with_sales = pd.DataFrame()    
buys_before_start = daily_positions[daily_positions['Type'] == 'Buy'].sort_values(by='Open date')

现在我们在一个数据框架中有了所有的买入,我们将过滤股票代码与卖出位置的股票代码相匹配的所有买入:

for position in buys_before_start[buys_before_start['Symbol'] == sale[1]['Symbol']].iterrows():

您会注意到,我们使用索引来访问数据中的“符号”列。这是因为使用iterrows()从索引[0]和数据序列[1]创建了一个元组。这与我们在遍历buys_before_start时使用索引的原因相同:

for position in buys_before_start[buys_before_start['Symbol'] == sale[1]['Symbol']].iterrows():
        if position[1]['Qty'] <= sale[1]['Qty']:
            sale[1]['Qty'] -= position[1]['Qty']
            position[1]['Qty'] = 0
        else:
            position[1]['Qty'] -= sale[1]['Qty']
            sale[1]['Qty'] -= sale[1]['Qty']
        stocks_with_sales = stocks_with_sales.append(position[1])

所以循环中发生的是在buys_before_start的每一次购买:

  • 如果最早的买入数量≤卖出数量(也就是你卖出的数量超过了你最初的买入数量),从卖出数量中减去买入数量,然后将买入数量设置为 0
  • 否则(某一天你买入的数量>卖出的数量),从买入位置减去卖出数量,然后从卖出位置减去相同的数量
  • 将调整后的位置附加到空的stock_with_sales数据帧中

一旦循环通过每个销售位置,您的代码现在将执行portfolio_start_balance的最后几行:

adj_positions_df = adj_positions_df.append(positions_no_change)
adj_positions_df = adj_positions_df.append(future_sales)
adj_positions_df = adj_positions_df[adj_positions_df['Qty'] > 0]

因此,我们在adj_positions_df中获取调整后的头寸,添加从未有过销售的回售头寸,添加未来发生的回售头寸,并最终过滤掉position_adjust清零的任何行。你现在应该有一个准确的记录,你的积极持有开始日期!

步骤 3 —创建每日性能快照

现在我们有了在开始日期持仓的准确报表,让我们创建每日业绩数据!我们的策略类似于我们在步骤 2 中所做的,事实上,我们将再次重复使用position_adjust方法,因为我们需要考虑我们的日期范围内的潜在销售额。我们将继续创建两个新函数,time_fillfifo,我将更详细地解释每个函数的作用:

时间填充

portfolio_start_balance类似,我们的目标是提供活跃头寸的数据框架,找到卖出头寸,并根据买入头寸将卖出头寸清零。这里的主要区别是,我们将使用有效交易日的market_cal列表进行循环:

sales = portfolio[portfolio['Type'] == 'Sell.FIFO'].groupby(['Symbol','Open date'])['Qty'].sum()
sales = sales.reset_index()per_day_balance = []
for date in market_cal:
        if (sales['Open date'] == date).any():
            portfolio = fifo(portfolio, sales, date)

这样,我们可以每天查看是否有销售发生,正确调整头寸,然后返回每日数据的正确快照。此外,我们还将过滤之前或当前date发生的头寸,并确保只有买入。然后,我们将在 market_cal 循环中添加一个带有当前dateDate Snapshot列,然后将其追加到我们的per_day_balance列表中:

daily_positions = portfolio[portfolio['Open date'] <= date]
daily_positions = daily_positions[daily_positions['Type'] == 'Buy']
daily_positions['Date Snapshot'] = date
per_day_balance.append(daily_positions)

先入先出

我们的fifo函数获取您的活跃投资组合头寸、在time_fill中创建的销售数据框架以及在market_cal列表中的当前date。然后对sales进行过滤,找出当前date发生的任何情况,并创建不受sales影响的位置的数据帧:

sales = sales[sales['Open date'] == date]
daily_positions = daily_positions[daily_positions['Open date'] <= date]
positions_no_change = daily_positions[~daily_positions['Symbol']. isin(sales['Symbol'].unique())]

然后,我们将使用我们可靠的position_adjust函数来清除任何活跃销售的头寸。如果在特定日期没有销售,我们的函数将简单地将positions_no_change附加到空的adj_positions数据帧上,为您留下准确的每日头寸快照:

adj_positions = pd.DataFrame()
for sale in sales.iterrows():
    adj_positions = adj_positions.append(position_adjust( daily_positions, sale))
adj_positions = adj_positions.append(positions_no_change)
adj_positions = adj_positions[adj_positions['Qty'] > 0]

运行这行代码应该会返回指定时间范围内所有交易日的列表,以及每天头寸的准确计数:

positions_per_day = time_fill(active_portfolio, market_cal)

步骤 4 —进行投资组合计算

如果你还跟着我们,我们就在最后阶段了!现在我们已经有了我们的活跃持仓量的准确的每日分类账,我们可以继续创建生成图表所需的最终计算!为此,我们将在代码中添加另外六个函数:

让我们从最后一个函数per_day_portfolio_calcs开始,因为它将使用所有其他函数。

每日投资组合计算

现在我们已经有了步骤 3 中的positions_per_day,我们的目标是将它与daily_benchmarkdaily_adj_closestocks_start一起传递给这个新函数:

combined_df = per_day_portfolio_calcs(positions_per_day, daily_benchmark, daily_adj_close, stocks_start)

然后,我们将使用pd.concat将我们的数据帧列表连接成一个列表:

df = pd.concat(per_day_holdings, sort=True)

现在我们有了一个大数据帧,我们将把它传递给per_day_portfolio_calcs中的其余函数。

修改后的每股成本

如果我们想跟踪每天的表现,我们需要知道我们每天持有股票的理论价值。这需要计算当前持有的证券数量,然后乘以每只证券的每日收盘价。

mcps = modified_cost_per_share(df, daily_adj_close, stocks_start)

为此,我们提供了新的单一 df 以及使用 yfinance 提取的每日数据,以及我们的开始日期。然后,我们将通过将投资组合快照的日期与每日数据的日期相结合,以及在自动收报机上相结合,来将我们的投资组合与每日收盘数据相结合。对于更熟悉 SQL 的人来说,这实际上是一个左连接:

df = pd.merge(portfolio, adj_close, left_on=['Date Snapshot', 'Symbol'],right_on=['Date', 'Ticker'], how='left')

一旦我们合并了df,我们将把每日收盘重新命名为“符号调整收盘”,然后用每日收盘乘以拥有的股票数量。删除额外的列将返回我们需要继续的数据帧:

df.rename(columns={'Close': 'Symbol Adj Close'}, inplace=True)
df['Adj cost daily'] = df['Symbol Adj Close'] * df['Qty']
df = df.drop(['Ticker', 'Date'], axis=1)

基准 _ 投资组合 _ 计算

现在我们有了证券的准确每日成本,我们希望将基准添加到数据集中,以便与我们的投资组合进行比较:

bpc = benchmark_portfolio_calcs(mcps, daily_benchmark)

我们首先使用类似于modified_cost_per_share中的合并将我们的每日基准数据合并到正确的快照中:

portfolio = pd.merge(portfolio, benchmark, left_on=['Date Snapshot'], right_on=['Date'], how='left')
portfolio = portfolio.drop(['Date'], axis=1)
portfolio.rename(columns={'Close': 'Benchmark Close'}, inplace=True)

现在我们已经将基准的每日收盘数据合并到我们的投资组合数据集中,我们将根据其最大和最小日期过滤我们的daily_benchmark数据。使用最大值和最小值来对比你的开始和结束日期是很重要的,因为最大值/最小值会考虑市场开放的日子:

benchmark_max = benchmark[benchmark['Date'] == benchmark['Date'].max()]
portfolio['Benchmark End Date Close'] = portfolio.apply(lambda x: benchmark_max['Close'], axis=1)benchmark_min = benchmark[benchmark['Date'] == benchmark['Date'].min()]
portfolio['Benchmark Start Date Close'] = portfolio.apply(lambda x: benchmark_min['Close'], axis=1)

太好了!因此,现在我们在投资组合数据集中也有了基准的绝对开始和结束收盘,这在计算每日回报时将非常重要。

投资组合 _ 年末 _ 统计

现在我们的基准数据已经添加完毕,让我们进入下一步:

pes = portfolio_end_of_year_stats(bpc, daily_adj_close)

我们这里的目标是获取benchmark_portfolio_calcs的输出,找到投资组合中所有股票的最后一天收盘,然后向我们的投资组合数据集中添加一个Ticker End Date Close列。我们将再次合并到每日股票数据中,过滤最大日期,然后根据股票代码进行连接:

adj_close_end = adj_close_end[adj_close_end['Date'] == adj_close_end['Date'].max()]portfolio_end_data = pd.merge(portfolio, adj_close_end, left_on='Symbol', right_on='Ticker')portfolio_end_data.rename(columns={'Close': 'Ticker End Date Close'}, inplace=True)portfolio_end_data = portfolio_end_data.drop(['Ticker', 'Date'], axis=1)

现在,在我们生成计算结果之前,只需再走一步!

投资组合 _ 年初 _ 统计

这最后一步采用更新的投资组合数据框架,即来自 yfinance 的每日股票数据,并为基准分配年初等价头寸:

pss = portfolio_start_of_year_stats(pes, daily_adj_close)

我们将首先过滤开始日期的每日收盘数据,然后使用股票代码将我们的投资组合数据合并到其中。为了方便起见,我们将这种关闭称为Ticker Start Date Close:

adj_close_start = adj_close_start[adj_close_start['Date'] == adj_close_start['Date'].min()]portfolio_start = pd.merge(portfolio, adj_close_start[['Ticker', 'Close', 'Date']], left_on='Symbol', right_on='Ticker')portfolio_start.rename(columns={'Close': 'Ticker Start Date Close'}, inplace=True)

然后我们需要调整每股成本,但是为什么呢?想象一下,你很久以前以 500 美元/股的价格购买了谷歌,但现在你想计算 2020 年你的头寸的 YTD 回报率。如果你用 500 美元作为你 2020 年初的成本基础,你不会有一个准确的比较,因为成本基础是多年前的。为了解决这个问题,我们将使用 Numpy 的where函数:

portfolio_start['Adj cost per share'] = np.where(portfolio_start['Open date'] <= portfolio_start['Date'],
         portfolio_start['Ticker Start Date Close'],
         portfolio_start['Adj cost per share'])

简单来说,这就是说‘如果开市日期≤开始日期的日期,那么Adj cost per share等于Ticker Start Date Close’(股票从 yfinance 数据上的 min 日期开始的收盘价)。如果没有,那就用现有的Adj cost per share

剩余部分根据修改后的每股成本修改调整后的成本,从合并中删除不需要的列,然后根据新计算的调整后成本计算您将拥有的基准股票的等量:

portfolio_start['Adj cost'] = portfolio_start['Adj cost per share'] * portfolio_start['Qty']
portfolio_start = portfolio_start.drop(['Ticker', 'Date'], axis=1)portfolio_start['Equiv Benchmark Shares'] = portfolio_start['Adj cost'] / portfolio_start['Benchmark Start Date Close']portfolio_start['Benchmark Start Date Cost'] = portfolio_start['Equiv Benchmark Shares'] * portfolio_start['Benchmark Start Date Close']

恭喜,我们现在已经有了正确计算回报的所有必要数据!让我们完成最后一部分,然后开始想象!

计算返回

这里的最后一步只是从所有其他函数中提取聚合数据帧,对我们一直在修改的数据应用一系列计算,并返回最终数据帧:

returns = calc_returns(pss)

第一组Benchmark ReturnTicker Return都使用当前收盘价除以其初始成本基础来计算回报:

portfolio['Benchmark Return'] = portfolio['Benchmark Close'] / portfolio['Benchmark Start Date Close'] - 1portfolio['Ticker Return'] = portfolio['Symbol Adj Close'] / portfolio['Adj cost per share'] - 1

每家公司的份额价值以相同的方式计算,使用我们之前计算的修改后的每日数量和等效基准份额:

portfolio['Ticker Share Value'] = portfolio['Qty'] * portfolio['Symbol Adj Close']portfolio['Benchmark Share Value'] = portfolio['Equiv Benchmark Shares'] * portfolio['Benchmark Close']

我们将再次做同样的事情来计算货币收益/损失,从我们在portfolio_start_of_year_stats函数中计算的修改后的调整成本中减去股票价值列:

portfolio['Stock Gain / (Loss)'] = portfolio['Ticker Share Value'] - portfolio['Adj cost']portfolio['Benchmark Gain / (Loss)'] = portfolio['Benchmark Share Value'] - portfolio['Adj cost']

最后,我们将使用之前计算的基准指标来计算绝对回报值:

portfolio['Abs Value Compare'] = portfolio['Ticker Share Value'] - portfolio['Benchmark Start Date Cost']portfolio['Abs Value Return'] = portfolio['Abs Value Compare']/portfolio['Benchmark Start Date Cost']portfolio['Abs. Return Compare'] = portfolio['Ticker Return'] - portfolio['Benchmark Return']

嘣!现在,让我们弄清楚如何绘制我们的新数据,并完成这项工作。

步骤 4 —可视化数据

那么,现在我们已经经历了所有这些来获得我们的日常性能数据,我们应该如何最好地显示它呢?每日数据的最大好处是可以看到你的头寸在一段时间内的表现*,所以让我们先来看看我们的汇总数据。*

在最近的项目中,我一直在使用 Plotly,所以为了做到这一点,我将选择 simple 并使用 Plotly Express 库。由于我们需要将每天的股票汇总到一个单一的每日指标中,我将把它写成一个函数,它采用您完成的数据框架和两个您想要相互对照的指标:

如您所见,我们将提供 ticker 和基准收益/损失作为指标,然后使用 groupby 将每日业绩汇总到投资组合级别。画出来应该会返回类似这样的东西!

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

每日累计损益与基准对比

您还可以使用不同的指标(如Abs Value Compare)进行聚合,将其视为一行:

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

使用绝对值比较度量显示

这很棒,但在我看来,最有用的视图可以通过使用 plotly express 中的facet_col选项来生成每个股票的图表,将基准与每个股票的性能进行比较:

我们还将使用facet_col_wrap参数来限制每行的图表数量。运行这段代码应该会生成类似下面的输出!

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

每只股票的基准回报比较示例数据

结论

我们在这里讨论了很多内容,希望这对学习更多关于填充和分析财务数据的知识有所帮助!未来还有很多可以探索的领域,包括:

  • 考虑分红和股票分割——yfinance 有这个数据,但我想在添加更多功能之前先发布这个数据
  • 加入更多奇异的指标,比如波动性比较和滚动平均。在投资组合层面与基准层面进行比较可能会很有意思
  • 改善可操作性的信号。例如,你可以利用日间数据,观察短期均线何时穿过长期均线,作为潜在买入/卖出头寸的信号

希望这对任何人都有帮助,并随时联系或在下面评论问题/评论。感谢阅读!

给疫情做模特

原文:https://towardsdatascience.com/modelling-a-pandemic-eb94025f248f?source=collection_archive---------17-----------------------

复杂系统

使用 Python 和 pandas 构建基于代理的模型

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

克里斯多夫·伯恩斯在 Unsplash 上拍摄的照片

在我们的日常生活中,我们会遇到许多复杂的系统,在这些系统中,个人会相互影响,例如股票市场或高峰时间的交通。为这些复杂系统找到合适的模型可以让我们更好地理解它们的动力学,并允许我们在变化的条件下模拟其行为。对复杂系统建模的一种方式是使用基于主体的模型,这意味着我们是明确地模拟个体和他们的相互作用,而不是以集合的方式推导系统的动态。

您可以在这里找到一篇关于基于代理的模型的介绍性文章:

[## 利用基于代理的模型克服复杂性

基于代理的模型初学者指南:它们如何工作和做什么

medium.com](https://medium.com/swlh/overcoming-complexity-with-agent-based-models-5c4cca37cc61)

在这篇文章中,我们想用 python 开发这样一个基于代理的模型。作为一个例子,我们试图模拟疫情的行为。请注意,我根本不是流行病学家。这篇文章的目标不是建立一个复杂的模型来预测现实生活,而是看看我们如何建立一个简单的基于主体的模型并研究一些由此产生的动力学。让我们从一些基本的考虑因素开始。

我们模型的基础

对于我们的例子,我们假设一种非致命的疾病可能在相互接触的个体之间传播。最基本的方法是考虑三个不同的群体:

  1. 尚未感染的个体,称为易感组。
  2. 感染并可能传播疾病的个体。
  3. 已经从疾病中康复的个体现在是免疫的。

因为有三个相关的组(Su 可接受, I 感染, R 恢复),这些模型也被称为 SIR 模型。

解析 SIR 模型

我们将从一个数学 SIR 模型开始,它将作为一个基准模型。在基本 SIR 模型中,三组之间的流量为:S -> I -> R。这是一条单行道,在这条单行道上,开始时大多数个体都属于 S 群体,最终通过 I 群体级联成 R 群体。在每个时间步 t 中,一定数量的个体从 S 穿越到 I 以及从 I 穿越到 R ,而个体总数 N = S+I+R 保持不变。我们可以将这些动力学写成一组微分方程,或者,以一种更容易理解的形式,我们可以写下每个组在某个时间步长内的变化量:

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

基本 SIR 模型

动态由两个变量 βγ 控制。 β 是传染性个体传染他人的速率, γ 是传染性个体恢复的速率。对于固定的 βγ 来说,这些动态特性如下所示:

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

你可以看到受感染个体的数量增长很快,在第 40 天左右达到高峰,此时易感个体的数量显著下降,感染率下降。这仅仅是因为到那时,相当数量的人已经感染了这种疾病,不能再被感染了。到最后,受感染个体的数量下降到零,根除了疾病。请注意,到那时,大约 20%的人从未被感染。这个所谓的稳态解也可以解析计算,并且取决于参数 βγ。

通过这个简单的 SIR 模型,我们已经可以观察到我们问题的一些基本动力学。然而,我们只是以一种综合的方式来看待我们的群体。我们假设个体是一个同质的、非结构化的集合,被组织成三个定义明确的、完全混合的群体。被建模的相互作用平均起来只有*。每个被感染的个体每天感染固定数量的接触,并且每天所有被感染个体中的恒定部分被治愈。在这个模型中,没有办法实现个人之间复杂的社会互动。为了放宽这些假设,我们现在将建立一个基于代理的模型,分别模拟每个个体。*

基于主体的模型

我们的第一个目标是再现解析 SIR 模型的结果。作为一种数据结构,我们希望使用熊猫数据帧。让我们从初始化 10,000 个代理开始,用数据帧中的行表示:

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

目前,数据帧只有一行称为 state ,表示代理的健康状态。我们用 0 编码易感,用 1 编码感染,用 2 编码康复。

现在我们需要一些功能来感染一个代理。我们希望这个函数获取与被感染的代理有过接触的代理的列表。此外,我们希望给出这些接触者实际被感染的概率。为了增加随机性,这里使用了一些蒙特卡罗方法。下面的函数完成所需的工作。

联系人列表允许多次保留同一个代理。我们为联系人列表中的每个唯一代理滚动 0 到 1 之间的随机数,并且如果该滚动低于概率阈值,则将状态从易感染(0)更新为已感染(1)。该函数的最后一行相应地更新 state 列。

同样,我们需要一个函数,以一定的概率恢复被感染的代理。这里,我们在每个时间步中使用一个平坦的恢复机会。

在每个时间步调用感染恢复函数。为此,我们创建了一个步骤函数。这里,我们生成一个随机接触列表,其长度为受感染代理数量的常数倍。

为了对我们基于代理的模型的结果的变化有一个感觉,我们将运行模拟十次。对于每个实验,我们初始化一组 10,000 个代理,开始时有 5 个被感染的患者,零个患者。然后我们执行 150 个时间步骤。

基线结果

在每个时间步可视化三个组(易感、感染和恢复)的每个组的大小,我们可以看到我们的基于代理的模型的动力学与基本 SIR 模型一致。

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

基线模型的结果。这里我们使用以下参数:_randomContacts=9,_chanceOfInfection=0.025 和 _chanceOfRecovery=0.1。对于 SIR-模型,参数为 β=0.225γ=0.1。

实线显示了我们 10 次模拟运行的中值,而阴影区域显示了 25%-75%分位数之间的区域。即使在模拟的中心部分存在一些差异,所有模型都达到了非常相似的终点,即等于解析解。

到目前为止,与基本的 SIR 模型相比,我们还没有获得太多,但是我们已经建立了一个基于代理的基线模型,并验证了它的行为类似。有了这个设置,我们现在可以开始添加额外的复杂性。

基于空间主体的模型

直觉上,假设被感染的病原体会与一组完全随机的病原体接触,在现实生活中可能不成立。你更希望有一些社会邻居,一群被感染的代理人定期接触的人。模拟这种效果的一个简单方法是将代理放在一个格子上,让它们与 9 个最近的邻居互动。

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

空间模型的结果。这里我们使用以下参数:_randomContacts=0,_chanceOfInfection=0.06 和 _chanceOfRecovery=0.1。对于 SIR 模型,参数为 β=0.54γ=0.1。

注意延长的 x 轴。您可以看到,对于基于空间代理的模型来说,动力学现在慢了很多。我甚至不得不显著增加感染的几率,让它继续运行。我们向接触者介绍的结构导致这样一个事实,即受感染的病原体生活在已经有许多病原体被感染或已经康复的环境中,从而导致疾病传播的显著减少。我们可以在下面的动画中直观地看到代理的空间分布:

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

蓝色:易感,黄色:感染,绿色:康复

添加随机联系人

我们发现,当我们将空间结构引入到个体的社会互动中时,疾病的发展速度明显减慢。除了九个空间邻居之外,我们为每个代理引入一个额外的随机接触会发生什么?

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

空间模型的结果。这里我们使用以下参数:_randomContacts=1,_chanceOfInfection=0.06 和 _chanceOfRecovery=0.1。对于 SIR-模型,参数为 β=0.6γ=0.1。

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

蓝色:易感,黄色:感染,绿色:康复

只需一次额外的随机接触,感染的动力学就再次变得更快,迅速破坏我们通过在网格上放置药剂而引入的结构。

日益增加的复杂性

我们有一个工作设置,人们现在可以通过增加复杂性来玩它。人们可以考虑对不同的、相互联系很弱的个体群进行建模,或者为个体引入一个年龄结构,以反映不同年龄组的不同类型的相互作用。此外,人们可以开始采取措施,在某个时间阶段减少感染的机会,或者减少接触的次数。

表演

一句话关于模型的性能。通常,我喜欢使用面向对象的方法来构建基于代理的模型。将代理建模为一个类使得模拟和编码非常直观。然而,在 python 中,模拟可能很快变得相对缓慢。通过将数据存储到 pandas dataframes 中,其中一行代表一个代理,我们失去了一些灵活性,但是我们可以依靠 numpy 函数来完成主要的工作负载,从而使模拟相当快。我的机器以每秒 50 步的速度运行了 100,000 个模拟代理,在几秒钟内产生模拟输出。

结论

我向您展示了如何从头开始建立一个基本的基于代理的模型。我们看了模拟疾病传播的例子。作为第一步,我们对照一个已知的数学模型来验证我们模型的最小版本。然后,我们开始改变参数,以研究系统的动态变化。通过在药物中引入晶格结构,我们观察到疾病的传播明显减慢,但只允许一次随机接触又会导致动力学增加。所提出的实现是一种灵活的设置,允许代理内更复杂的交互、异构性和结构的简单实现。此外,我们能够在一个复杂的大规模模拟中研究个体水平上的代理或代理的子群。

请随意使用这个设置作为开始,并与它一起玩。完整代码可在此处获得:

*[## 比特桶

贮藏室ˌ仓库

bitbucket.org](https://bitbucket.org/chgraf/blog/src/master/Agent%20Based%20Pandemic/)*

为药物发现知识图建模生物医学数据

原文:https://towardsdatascience.com/modelling-biomedical-data-for-a-drug-discovery-knowledge-graph-a709be653168?source=collection_archive---------22-----------------------

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

听取阿斯利康的数据科学和人工智能的汇报

本月早些时候,阿斯利康的副首席科学家娜塔莉·库尔巴托娃加入了我们的第一系列轨道

Natalie 在阿斯利康的数据科学和人工智能部门工作,在那里她专注于数据建模、将数据集成到知识图、预测算法以及其中的主题。

目标:预测新的疾病目标

在阿斯利康,Natalie 的团队专注于构建一个知识图来预测新的疾病目标(基因或蛋白质目标),他们称之为发现图。在构建过程中,Natalie 向我们介绍了两种要考虑的数据源:

结构化数据指的是生物信息学中公开可用的数据集,这些数据集已经在行业中进行了整理和广泛使用。虽然生物医学结构化数据是机器可读的,但通常不容易处理。特别是,很难集成这些数据集,因为它们可以以不同的方式描述类似的概念,例如:不同的 id 彼此不一致。一些最常用的公开可用数据集包括: EnsemblUniprotChEMBLPubChemOmniPathReactome 、GO、CTD、human protein tlas

非结构化数据指来自文本的数据。为了处理这个,我们需要使用 NLP(自然语言处理)管道,然后处理它们的输出。在这里,困难在于这些数据往往是杂乱和嘈杂的。对于他们的 NLP 引擎,娜塔莉的团队使用了开源库 SciBERT 以及阿斯利康的专有工具。

Natalie 然后向我们介绍了她的团队为他们的发现图构建的模式(幻灯片如下)。

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

TypeDB Studio 中的可视化模式——关注已定义的实体。经许可重新发布的幻灯片。

娜塔莉的团队主要对研究compoundsdiseasesgene/proteins感兴趣——他们亲切地称之为金三角。这些实体之间的连接需要尽可能的稳固和可靠,这意味着将所有可能的、相关的数据源吸收到它们的发现图中。

这个发现图每天都在变大。到今天为止,它已经广泛地包含了这三种实体类型:

gene-protein:19371
disease:11565
compound:14294
实体类型之间还有 656206 个关系。

他们是如何模拟这个“金三角”的?

娜塔莉接着解释了她是如何对发现图的每一部分建模的,并一路举例。

模拟基因和蛋白质

首先,她的团队着眼于如何模拟基因和蛋白质。Natalie 的团队没有将这些分成两个实体,而是决定将它们建模为一个实体,他们称之为gene-protein。这有助于减少噪声和偏差。

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

在 TypeDB Studio 中可视化的[基因-蛋白质]实体。经许可重新发布的幻灯片。

相关属性gene-namechromosome,指定相应的基因名称,以及该基因所在位置的染色体名称。gene-id属性被建模为一个抽象,它包含每个gene-protein的唯一 id。

💡 There may have been scenarios where a parent attribute is only defined for other attributes to inherit, and under no circumstance do we expect to have any instances of this parent. To model this logic in the schema, use the abstract keyword.

interaction关系建立了gene-protein之间的交互,其中一个扮演 gene-source的角色,另一个gene-target。这种关系在预测新的疾病靶点中特别重要,因为它连接了基因和蛋白质之间的调节相互作用。

建模化合物

娜塔莉的团队将小分子和抗体放在一起,而不是将它们分开,作为一个实体类型,他们称之为compound。对于这些数据,他们使用了数据源:PubChem 和 ChEMBL。

这些数据库大约 95%相同,但有些化学物质只存在于两个来源中的一个。为了处理这些独特的化学物质,他们决定分配一个chembl-id作为主要 ID,如果它有一个,但是如果它没有那个 ID,那么他们将使用pubchem-id

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

在 TypeDB Studio 中可视化的[复合]实体。经许可重新发布的幻灯片。

正如我们在 Natalie 的幻灯片上看到的,compound用更多的属性建模。属性compound-id被用作一个抽象属性,其子属性preferred-compound-idpubchem-idchembl-iddrugbank

为了获得preferred-compound-id属性的值,他们使用两个规则来根据下面的逻辑分配chembl-idpubchem-id:

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

用于将适当的(ID)分配给特定[化合物]的规则。经许可重新发布的幻灯片。

第一个规则将chembl-id的值(如果存在的话)附加到preferred-compound-id。第二个规则首先检查compound是否没有chembl-id,如果是,那么它将pubchem-id属性的值附加到preferred-compound-id。这使得在特别查询化合物时,很容易查询该属性。

建模疾病

娜塔莉解释说,要建模的最复杂的概念是疾病。这是因为疾病有多个本体,每个本体都有不同的 id。例如,一种疾病可能有来自不同本体和疾病层次的多个 id。在下面的幻灯片中,娜塔莉向我们展示了三个数据源的层次结构:EFO、蒙多和多德。

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

疾病数据源的本体论层次。经许可重新发布的幻灯片。

这种异质性的原因是,最初,这些本体是在不同的子域中设计的:例如,医生使用 Orphanet,而医学研究小组可能使用另一个:EFO 或 MONDO。这导致数据互不关联,互不相干。Natalie 和她的团队希望能够建立一个疾病实体模型,在这些本体之间交叉引用。

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

数据是从哪里来的?经许可重新发布的幻灯片。

因为这个挑战,他们选择了建模两种实体类型:diseaseontology。他们使用一个disease-hierarchy关系——一个三元关系——来连接这两者。

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

在 TypeDB Studio 中可视化的[疾病]实体。经许可重新发布的幻灯片。

有了这个disease-hierarchy关系,Natalie 和她的团队能够编写有用的查询,例如:

给我所有“慢性肾脏病”患儿的疾病节点使用 EFO 本体。

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

示例查询和层次结构中的预期路径。经许可重新发布的幻灯片。

这个查询展示了模型的强大功能——使用 TypeDB 的模式的强大功能——因为即使他们只是询问高级疾病,查询也将返回该疾病的所有可能的子类型

为了对此建模,Natalie 再次利用 TypeDB 的规则引擎:

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

TypeDB 中的传递性规则示例。经许可重新发布的幻灯片。

上面幻灯片中显示的规则single-ontology-transitivitydisease-hierarchy关系中创建了一个传递性。这个规则的结果是,如果查询一个扮演 superior-disease的疾病,将会推断出扮演 subordinate-disease的所有疾病。此外,这意味着即使您不指定您要查询的本体,您仍然会返回所有摄取的数据源中该特定父疾病的所有从属疾病、

当特定的本体中没有相应的引用 ID 时,这种类型的规则特别有用。

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

将疾病实体映射到适当的[疾病 id]。经许可重新发布的幻灯片。

正如 Natalie 向我们展示的,当某个disease-id不存在于某个本体中时,我们可以沿着层级向上,直到我们找到一个确实存在的 ID,然后分配那个 ID。为此,Natalie 使用了另一个传递规则:

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

当一个(id)不存在时,这个传递性规则从父节点向下拉。经许可重新发布的幻灯片。

第一个规则determine-best-mesh-id-1,如果一个mesh-id已经存在,就给一个disease实体分配一个best-mesh-id属性。然后,第二个规则声明,如果我们不知道mesh-id,我们希望从父疾病中删除mesh-id。娜塔莉强调了这是多么有效,以及在实践中结果是多么积极。

数据整合

一旦领域被建模,我们就可以开始接收数据了。为此,有两种方法可以采用:

  • **数据工厂:**在加载到知识图之前集成数据
  • **数据模式:**通过灵活的数据加载和后续推理,数据集成发生在知识图中

Natalie 的研究小组使用数据模式方法,使用知识图本身作为整合数据的工具。这对一些开发者来说可能是反直觉的。

如果您设想添加额外的未知数据源,那么您必须足够灵活地操作数据库中的数据。这使得复制另一个人的工作[论文]来验证假设。

在研究工作的情况下,灵活性是必不可少的,正如 Natalie 在之前的冲突 mesh-id 中向我们展示的那样。

如上所述,解决方案是使用 TypeDB 的推理引擎。

摘要

最后,Natalie 花了几分钟总结了这种方法在数据模式和逻辑推理方面的优势。

其他图形数据库在数据加载方面很灵活,但缺乏验证。虽然灵活性很重要,但是验证对于保持数据的一致性也是必要的。

几个月前,Natalie 的一个初级同事不小心将电影数据加载到了他们的生物医学知识图表中。第二天早上,团队的其他成员惊讶地看到他们的生物医学数据中有电影和演员!

使用 TypeDB,在插入时通过数据模式对数据进行逻辑验证,以确保正确的数据进入知识图。

Natalie 认为这个数据库在正式的模式设计、逻辑推理和预测算法能力之间提供了一个很好的平衡。根据她的经验,在将数据加载到知识图之前,需要先对数据进行建模。和灵活的模式有助于找到这种理想的平衡。

💡 Note that all prediction algorithms depend on what you load in the database — aka: be careful what you put in the knowledge graph. The noise level should be as low as possible.

最后,她谈到了我们是在加载数据之前还是之后集成数据的关键选择,在他们的情况下(如上所述),一旦插入数据库,就在加载之后完成。

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

特别感谢娜塔莉和她在阿斯利康的团队的热情、奉献和透彻的解释。

你可以在 Vaticle 的 YouTube 频道这里找到完整的演示。

建模分类树

原文:https://towardsdatascience.com/modelling-classification-trees-3607ad43a123?source=collection_archive---------45-----------------------

如何编写最流行的机器学习算法之一(Python)

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

凯利·西克玛在 Unsplash 上的照片

决策树是机器学习中最流行的算法之一:它们易于可视化,高度可解释性,超级灵活,可以应用于分类和回归问题。DTs 通过学习从数据特征中推断出的简单决策规则来预测目标变量的值。

在我的帖子 " 决策树完全指南 s" 中,我详细描述了 DT:它们在现实生活中的应用,不同的 DT 类型和算法,以及它们的优缺点。现在是务实的时候了。你如何建立一个 DT?你如何将它应用到真实数据中?DTs 只不过是算法(或步骤序列),这使得它们非常适合编程语言。让我们看看怎么做。

问题是

世界银行将世界经济分成四个收入组:

  • 高的
  • 中上
  • 中下
  • 低的

这一分配基于使用图册方法计算的人均国民总收入(GNI)(以现值美元计算),类别定义截至 2018 年 7 月 1 日。利用数据预处理技术,我创建了一个数据集,其中还包括其他国家的变量,如人口、面积、购买力、GDP 等。你可以在这个链接下下载数据集。

该分类树的目标是根据数据集中包含的变量预测一个国家的收入组。

台阶

您可以通过处理更简单的子步骤来降低构建 DTs 的复杂性:DT 中的每个单独的子例程都将连接到其他子例程以增加复杂性,这种构造将让您获得更健壮的模型,更易于维护和改进。现在,让我们用 Python 建立一个分类树(特殊类型的 DT)。

加载数据并描述数据集

加载数据文件是最简单的部分。问题(也是最耗时的部分)通常是指数据准备过程:设置正确的数据格式、处理缺失值和异常值、消除重复值等。

在加载数据之前,我们将导入必要的库:

import xlrd
import pandas as pd
import numpy as np
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split

现在我们加载数据集:

df_c = pd.read_excel(“macrodata_class.xlsx”)

看一下数据:

df_c.head()

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

我们可以看到有一些缺失的值。让我们检查变量的类型:

df_c.info()

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

在总共 215 个条目中,几乎所有的列都有缺失值。同样,我们可以看到,除了从变量“国家”和“类”(我们的目标变量)被定义为“对象”,其余的都是数字变量(float64)。

我们可以计算每个变量中有多少个缺失值:

print(df_c.isnull().sum())

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

让我们在构建 DT 之前去掉那些缺失的值:

df_c.dropna(inplace=True)

如果我们再次描述我们的数据集,我们会看到这些寄存器已被删除:

df_c.info()

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

您可以在链接上找到探索性数据分析的其他技术。

选择特征和目标变量

您需要将给定的列分成两种类型的变量:因变量(或目标变量)和自变量(或特征变量)。首先,我们定义特性:

X_c = df_c.iloc[:,1:8].copy()

然后是目标变量:

y_c = df_c.iloc[:,8].copy()

分割数据集

要了解模型性能,将数据集分为定型集和测试集是一个好策略。通过将数据集分成两个独立的集合,我们可以使用一个集合进行训练,使用另一个集合进行测试。

  • **训练集:**这个数据用来建立你的模型。例如使用 CART 算法来创建决策树。
  • **测试集:**该数据用于查看模型在看不见的数据上的表现,就像在现实世界中一样。在您想要测试您的模型以评估性能之前,这些数据应该是完全不可见的。
X_c_train, X_c_test, y_c_train, y_c_test = train_test_split(X_c, y_c, test_size=0.3, random_state=432, stratify=y_c)

这里有几件事:我们将数据集分为 70%的训练和 30%的测试,并执行分层抽样,以便产生的样本中值的比例与目标变量中提供的值的比例相同。

建立 DT 模型并微调

构建 DT 就像这样简单:

clf = DecisionTreeClassifier(criterion=’gini’,min_samples_leaf=10)

在这种情况下,我们只定义了分裂标准(选择了基尼指数而不是熵),并且只定义了一个超参数(叶子的最小样本量)。定义模型架构的参数被称为超参数,因此,搜索理想模型架构(最大化模型性能的架构)的过程被称为超参数调整*。* A 超参数是在学习过程开始前就设定好值的参数,它们不能直接从数据中训练出来。

您可以通过调用模型来查看可以优化的其余超参数:

clf

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

模型可以有许多超参数,并且有不同的策略来寻找参数的最佳组合。你可以在这个链接上看看其中的一些。

列车 DT 模型

将模型拟合到训练数据代表了建模过程的训练部分。在模型定型后,可以使用预测方法调用来进行预测:

model_c = clf.fit(X_c_train, y_c_train)

测试 DT 模型

测试数据集是独立于训练数据集的数据集。该测试数据集是您的模型的未知数据集,有助于您对其进行概化:

y_c_pred = model_c.predict(X_c_test)

设想

DTs 最大的优势之一是它们的可解释性。可视化 DTs 不仅是理解模型的有效方法,也是传达模型工作原理的有效方法:

from sklearn import tree
import graphviz
dot_data = tree.export_graphviz(model_c, feature_names=list(X_c), class_names=sorted(y_c.unique()), filled=True)
graphviz.Source(dot_data)

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

“purchasing.power.cap”这个变量似乎对于定义阶层或目标变量非常重要:高收入国家位于右侧,中上收入位于中间,低/中下收入位于左侧。

评估绩效

评估你的机器学习算法是任何项目必不可少的一部分:你如何衡量它的成功,你什么时候知道它不应该再改进了?不同的机器学习算法有不同的评估指标。让我们提一下分类树的一些主要方法:

准确度得分

分类问题的准确性是指模型在各种预测中做出的正确预测的数量。

print ('Accuracy is:',(accuracy_score(y_c_test,y_c_pred)))

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

我们的准确率是 71.9%。如果我们认为可以通过生成新的特征或调整超参数来改进我们的模型,这还不错。但这是一个全球指标,所以让我们用其他指标来了解更多细节。

混乱矩阵

混淆矩阵是用于发现模型的正确性和准确性的最直观的度量之一。它用于分类问题,其中输出可以是两种或多种类型的类。为了理解它,首先,我们需要定义一些术语:

  • 真阳性(TP) :显示一个模型正确预测阳性病例为阳性。被诊断为存在的疾病确实存在。
  • 假阳性(FP) :显示一个模型错误地预测阴性病例为阳性。E 例如,被诊断为存在的疾病不存在(I 型错误)。
  • 假阴性(FN): 表示一个模型错误地将阳性病例预测为阴性*。例如,存在被诊断为不存在的疾病(类型 II 错误)。*
  • 真阴性(TN): 表明模型正确预测阴性的情况为阴性。被诊断为不存在的疾病是真正不存在的。
cmatrix = confusion_matrix(y_c_test,y_c_pred, labels=y_c_test.unique())
pd.DataFrame(cmatrix, index=y_c_test.unique(), columns=y_c_test.unique())

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

在像我们这样的多类混淆矩阵的情况下,矩阵将扩展到类的数量(在我们的例子中是 4 x 4)。我们的 DT 正确预测了 19 个“高收入”实例中的 17 个,8 个“低收入”实例中的 6 个,13 个“中低收入”实例中的 8 个,以及 17 个“中高收入”实例中的 10 个。

关于多类混淆矩阵的完整解释,请查看本文

分类报告

分类报告显示了每个类别的主要分类指标。这给出了对分类器行为超过全局准确性的更深的直觉,这可以掩盖多类问题中的一类中的功能弱点。分类报告集成了不同的指标,例如:

  • 精度(TP/(TP+FP): 是正确预测的正观测值与总预测正观测值之比。对于每个类别,它被定义为真阳性与真阳性和假阳性之和的比率。换句话说,当预测正面实例时,分类器有多“精确”?
  • Recall (TP/(TP+FN): 是分类器找到所有正例的能力。对于每个类别,它被定义为真阳性与真阳性和假阴性之和的比率。换句话说,“对于所有实际上为阳性的实例,正确分类的百分比是多少?”
  • *F1-Score (2(精度*召回)/(精度+召回))😗*是精度和召回的加权调和平均值,使得最好的分数是 1.0,最差的是 0.0。一般来说,F1 分数低于准确度,因为它们将准确度和回忆嵌入到计算中。根据经验,F1 的加权平均值应该用于比较分类器模型,而不是全局精度。
  • Support: 是指定数据集中该类实际出现的次数。训练数据中的不平衡支持可以指示分类器的报告分数中的结构弱点,并且可以指示分层采样或再平衡的需要。支持在不同的模型之间不会改变,而是对评估过程进行诊断。
report = classification_report(y_c_test, y_c_pred)
print(report)

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

特征重要性

另一个关键指标包括为预测模型的输入特征分配分数,表明每个特征在进行预测时的相对重要性。特征重要性提供对数据、模型的洞察,并代表降维和特征选择的基础,这可以提高预测模型的性能。越多的属性用于 DT 的关键决策,其相对重要性就越高。

for importance, name in sorted(zip(clf.feature_importances_, X_c_train.columns),reverse=True):
 print (name, importance)

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

变量“purchasing.power.cap”相对于所有其他变量(是模型的主要特征)来说非常重要,如果我们考虑目标变量,这完全有意义。

包裹

虽然我们在建模期间涵盖了几个步骤,但这些概念中的每一个都是独立的学科:探索性数据分析、特征工程或超参数调整都是任何机器学习模型的广泛而复杂的方面。你应该考虑更深入地研究那些学科。

此外,DTs 是称为集成方法的更强大算法的基础。集成方法将几个 DTs 结合起来,产生比单个 DTs 更好的预测性能。系综模型背后的主要原理是一群弱学习者走到一起形成强学习者,显著提高单个 DT 的表现。它们用于减少模型的方差和偏差,并改进预测。既然你已经看到了决策树是如何工作的,我建议你继续使用像打包或推进这样的集合方法。

对这些话题感兴趣?关注我Linkedin Twitter

将新冠肺炎病毒在加拿大的传播建模为指数增长

原文:https://towardsdatascience.com/modelling-covid-19-spread-in-canada-as-exponential-growth-6938b4a41b5?source=collection_archive---------25-----------------------

指数增长及其如何应用于加拿大新冠肺炎传输的简要回顾。

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

指数函数的一般方程

编者按: 走向数据科学 是一份以数据科学和机器学习研究为主的中型刊物。我们不是健康专家或流行病学家,本文的观点不应被解释为专业建议。想了解更多关于疫情冠状病毒的信息,可以点击 这里

介绍

鉴于几天前加拿大的新冠肺炎病例数超过了 1000 例,我认为现在是回顾指数增长的适当时机。在这篇文章中,我将快速演示为什么加拿大的病例总数继续呈指数增长是令人担忧的。声明一下,我不是流行病学家——只是一个对数学建模感兴趣的人。

指数增长引物

如果你有一段时间没上过数学课,我们来简单回顾一下指数增长。如果你 20 多岁,那么我敢打赌,由于指数增长的性质,你已经有几个人建议你开始投资了。对于那些能够留出额外资金的人来说,这是一个好主意。假设你以每年 8%的复利投资 100 美元,在第一年年末你将有 108 美元。如果你把你的 108 美元再投资,第二年后你会有 116.64 美元。如此重复 30 年后,你会有超过 1000 美元。虽然你可能需要几年时间才能开始注意到任何重大收益,但随着时间的推移,你账户的余额会越来越快。这是为什么呢?指数增长的显著特征是它的变化率在增加。同样,它的变化率也在增加。还有,它的变化率,它的变化率是递增的,等等。尽管指数曲线的第一部分可能看起来很平坦,但很难想象这种快速变化仍在发生,只是规模较小。

新冠肺炎以指数增长的方式传播

指数增长的原理可以应用于新冠肺炎的传输。如果每个感染者多感染 2 个人,那么总感染人数就乘以了 3!就像你的投资一样,它开始时很慢,但随着时间的推移会以越来越大的速度增长(这对早期投资者来说是好事,但对传染病来说是坏事)。

图 1 显示了 1 月 25 日至 3 月 21 日加拿大确诊的新冠肺炎病例总数。这个数据是在https://covid 19 tracker . ca找到的,它一直保存着每天病例总数的历史记录[1]。从数据点可以看出,它们遵循指数增长模式。假设指数增长,非线性最小二乘分析被应用于得出指数函数的方程以模拟这种增长。该等式打印在下面的图 1 中。很明显,指数函数很好地拟合了(目前收集的)数据。

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

图 1: 截至 3 月 21 日,加拿大新冠肺炎病例总数。指数函数被拟合到数据中。t 是自 2020 年 1 月 25 日起的天数。

含义

如果新冠肺炎的传播继续遵循下图所示的指数增长模型,病例数将继续快速上升。图 2 显示了这个模型到 4 月 21 日的推断。如果这个指数函数继续准确地模拟新冠肺炎的总病例数,似乎我们可以在一个月内接近 200 万例。这仅占我国人口的 5%多一点。我们的医疗系统将不堪重负。你可以看到底部的图表在 3 月 21 日之前看起来是平的,考虑到正在发生的增长类型,这是误导性的。如果病毒继续按照这个模型的速度传播,我们没有希望很快“拉平曲线”。只有降低传输速率,才能做到这一点。

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

图 2: 假设我们拟合的指数模型仍然准确,推测加拿大截至 4 月 21 日(含 4 月 21 日)的新冠肺炎病例总数。

结论

让我们通过尽力练习社交距离来证明这个模型是错误的。通过限制与他人的接触,你正在为“打破”这种模式做出贡献!如果我们成功地减少了传播,也许我会写一篇关于 sigmoid 增长曲线的后续文章(“曲线变平”的结果)。在那之前,享受外面的好天气和与家人在一起的宝贵时间。

参考

小小的。加拿大新冠肺炎跟踪者 (2020),COVID19Tracker.ca

模拟信用卡欺诈

原文:https://towardsdatascience.com/modelling-credit-card-fraud-detection-e3006dd212ab?source=collection_archive---------41-----------------------

人为平衡的数据总是最佳选择吗?

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

插图。来源: Pixabay

信用卡诈骗是世界上一个“仍在增长”的问题。据估计,2018 年欺诈损失超过 270 亿美元,预计未来几年仍将大幅增长,如本文文章所示。

随着越来越多的人在日常生活中使用信用卡,也增加了犯罪分子从中赚钱的机会。新技术的发展使得罪犯和信用卡公司都在不断地改进他们的系统和技术。

鉴于这一金额,机器学习对信用卡公司来说肯定不是一个新词,早在它成为一种趋势之前,它们就已经在这方面进行了投资,以创建和优化风险和欺诈管理模型。这个来自 Visa 的快速视频以友好的方式展示了这个价值数百万的复杂系统的冰山一角。

在这本笔记本中,我将使用匿名信用卡交易数据开发一个机器学习模型,以展示一个稍微简单的模型在欺诈检测方面可以实现什么。我也会从实用的角度来讨论一些选型的相关点。

信用卡登记簿被视为个人信息,不能公开共享。所使用的数据集是来自实际卡使用的文件,但是使用称为主成分分析的方法掩盖了变量。这种方法不仅改变变量的名称,还改变变量的数值,用于降维。然后,我们可以在不识别任何个人信息的情况下处理真实数据。

数据集包含欧洲持卡人在 2013 年 9 月进行的交易,并在 Kaggle 中共享。

探索性分析

在我们实际创建机器学习模型之前,对数据有一个大致的了解是很重要的。分布、趋势、异常值,这些都是重要的信息,有助于在模型开发过程中创建和验证可能相关的理论。由于数据集涉及个人和财务信息,变量被 PCA 方法“掩盖”,不具有实际意义。因此,可以从实际分析中提取的信息急剧减少。但是让我们利用现有的资源。首先,我将创建一个计数图,显示数据集中有多少正常和欺诈交易。

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

为每堂课计算绘图。由作者创作。

甚至很难看到欺诈交易的杠杠。在 284807 笔交易中,只有 492 笔被发现是欺诈。这意味着,正如所料,这是一个非常不平衡的数据集,只有不到 1%的数据被归类为 1。

我还将创建一个按正常(0)和欺诈(1)分类的交易金额(欧元)的箱线图。

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

每类金额的箱线图。由作者创作。

箱形图显示了分布的偏斜程度,但仍有许多异常值超出了此图中 y 轴的限制。有趣的是,欺诈实际上比真实交易的中位数要低。也许犯罪分子想避开大额交易中常见的障碍和额外要求。但这能保护他们的行动不被发现吗?

模型开发

既然探索性分析已经完成,就可以创建一个预测模型来避免将来的欺诈。我不会关注研究中使用的代码、库或方法,而是关注我为获得结果所遵循的步骤。你可以在我的 Github 里看到完整的分析。

训练测试集分割

第一步是将可用数据分成两组。第一组称为训练集,将用于训练模型,选择最佳模型并调整其参数。第二个称为测试集,用作模型的测试。它就像是新数据一样提供,以便模型预测可以与每个操作的实际分类进行比较。

平衡

当数据集在类的数据之间有很大差异时,它被认为是不平衡的。为了确保模型能够正确地检测两个类的模式,其中一个选项是人工平衡数据集,使所有类的信息数量相似。我使用了随机下划线方法,最终每个类的点数分布均匀(数据集也小得多)。该过程导致整个数据集的 99%以上被排除在模型开发阶段之外。

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

为每堂课计算绘图。由作者创作。

正常化

一些机器学习模型可能更依赖于变量的数量级。例如,这意味着高阶变量可能被误解为与最终输出更相关。为了避免这种情况,归一化是可以使用的方法之一,因为它将转换所有变量,使它们具有标准的标度。我决定将数据标准化,以确保这些数量级不会影响我的结果。

型号选择

有许多机器学习模型可以用来解决一个问题。对于何时使用每一种都有通用的指导原则,但是强烈建议进行多项测试,以了解哪一种对每种情况都具有最佳的可预测性。这是一个分类问题,目标是根据交易的属性来预测交易属于哪个类别。最常用的分类模型是逻辑回归和分类树,但还有许多其他可用的模型。在这个例子中,我测试并比较了其中的一些,以确定哪一个是使用交叉验证方法的最佳选择。我根据每个模型的召回率做出决定,召回率是模型正确预测的真实类别总数的百分比。

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

每个型号的召回分数。由作者创作。

不同型号的召回结果非常相似。基于这些结果,我决定继续使用 XGBClassifier 模型。

超参数调谐

机器学习模型具有定义它们将如何学习和预测结果的重要参数。不同的参数导致不同的模型,从而导致不同的结果。由于模型的复杂性,没有分析方法来定义哪种参数组合将提供最佳结果。超参数调整是一种迭代方式,计算机测试不同的参数组合并计算分数以定义最佳组合。

在我的分析中,我测试了模型参数的许多组合,并找到了一个可以进一步改善结果的组合,但在召回率方面没有真正有意义的改善。

模型验证

模型验证的步骤是最等待的一步,因为它显示了新数据的分类是好是坏。让我们看看我的模型在分类报告中的表现。

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

模型分类报告。由作者创作。

这些都不是很好的结果。召回结果表明,该模型确实能很好地检测欺诈行为。但是,精度显示它也会有许多误报(正常交易被识别为欺诈)。这在混乱矩阵中会更清楚。

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

预测混乱矩阵。由作者创作。

可以看到,超过 3000 个正常操作被模型错误地分类为欺诈。这将意味着超过 3000 笔交易被阻止,消费者感到恼火,从而发现 82 起欺诈。这是因为该模型正在识别这些操作中的欺诈模式。如果这些模式不明显,这可以通过调整模型阈值来解决。

为了解释什么是阈值,让我们回顾一下分类模型是如何工作的。基于观察到的数据,它估计了它属于研究中的每个类的概率。基于概率,将点分类到一个类中,因此存在一个参考概率,在该参考概率之上点被分类。这个参考概率被称为阈值,并且可以被调整以适合特定分析的需要并改进分类结果。每个阈值的精确度和召回率的结果可以在精确度-召回率曲线中检查,所以我决定画一条曲线,看看是否有更好的阈值。

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

精确召回曲线。由作者创作。

曲线显示,可供选择的好阈值选项并不多。这意味着模型认为正常交易的模式与欺诈非常相似。似乎平衡数据集大大减少了正常交易的信息,以至于模型无法正确区分它们。所以我决定使用实际的原始数据集,看看不平衡类的结果是否会更糟。

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

不平衡数据集的模型分类报告。由作者创作。

分类报告在精确度和召回率的结果上显示出更多的均等。让我们看看混淆矩阵。

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

不平衡数据集的预测混淆矩阵。由作者创作。

现在,被认定为欺诈的正常业务数量大幅下降。因此,该模型无法预测像第一个案例中那样多的欺诈。预测和召回的结果可以在精确召回曲线中再次观察到。

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

不平衡数据集的精确召回曲线。由作者创作。

在曲线中,可以看到有一个阈值提供了 0.8 的精确度和 0.82 的召回率。在我看来,这是一个有趣的选择,但根据最终目标,有许多组合可以使用。似乎使用整个数据集创建的模型能够更好地识别正常交易与欺诈的模式。因此,当它看到新数据时,预测的欺诈总数从 3000 多起下降到 75 起,这是一种更加自信的方法。

结论

在这篇文章中,我开发了一个机器学习模型来预测信用卡交易中的欺诈行为。该分析涉及不同模型的测试,以基于召回、超参数调整以及使用精确召回曲线的分类阈值调整来选择最佳选项。

通过在分析中使用平衡的和不平衡的数据集,我证明了在某些情况下,使用不平衡的数据集可以获得良好的预测结果,并且在寻找问题的最佳解决方案时,“一刀切”的思维模式是没有帮助的。

使用不同的数据集和阈值,我能够预测数据集中 82%的欺诈,并保持高精度,因此正常交易不会频繁受阻。

当然,没有完美的模型,总会有一个时刻,项目团队必须决定哪种折衷最适合其目标。在本例中,决策点是用平衡或不平衡的数据开发模型。这导致了不同的精确度和召回率。另一个决策是哪个阈值在欺诈检测和误报方面带来最佳平衡。如果公司有非常严格的安全政策,它可以选择尽可能高的召回率,不管精确度是多少。另一方面,一些公司更愿意允许一些欺诈行为,以确保他们的客户在正常交易中不会被卡。

好消息是,无论目标和限制是什么,数据科学中工具和技术的多样性和灵活性允许它应用于许多不同的情况。对此的主要要求是明确定义目标和度量标准。基于此,一个有经验的团队可以在研究过程中运用批判性思维找到最佳解决方案。

谢谢大家!

感谢你阅读这份材料,我希望它能在某些方面对你有用。如果你想了解分析的更多细节,你可以查看我的 GitHub 中的完整研究。在接下来的几周里,我会发布一些非常好的文章,如果你喜欢这篇文章,请查看我的媒体LinkedIn 个人资料!感谢您的阅读!

系统模型化

原文:https://towardsdatascience.com/modelling-e096495d8d70?source=collection_archive---------42-----------------------

通常简化为仅仅训练一个模型,这实际上是将预测与现实世界联系起来。

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

图片由皮克斯拜的 Gerd Altmann 提供

最终确定模型

我们已经讨论了使用交叉验证来帮助我们选择适合我们问题的最佳算法。值得注意的是,通过五重交叉验证,我们对一个模型进行了五次训练和测试。每次模型用 80%的数据进行训练,用剩下的 20%进行测试。但是我们最终得到了这些训练有素的模型中的哪一个呢?答案其实是最终的模型是在全训练数据集上重新训练的。毕竟,我们已经通过保留数据来评估模型性能;我们不必再保留一个分区了。为什么要浪费呢?我们可以用这额外的 20%来制作一个模型,这个模型应该更加稳健,因为它已经在更多的数据上进行了训练。

同样,一旦我们完成了所有的交叉验证和模型选择,并使用我们的最终坚持测试集(整个过程之前没有见过)检查了模型性能,我们实际上甚至可以将该数据集纳入我们的训练,以希望产生稍微好一点的结果。我们只需要记住,我们不能对我们的算法设计做任何进一步的调整。这是需要注意的重要一点。我们不能决定让我们的模型变得更复杂一点,只是为了在这个特定的样本上做得更好一点。我们不能决定多一点或少一点正规化是正确的做法。我们可能会感叹,我们已经失去了对算法性能的独立评估,但如果我们乐于接受我们的测试集是好数据,并因此给了我们有效的性能检查,我们应该乐于将这些数据纳入我们的模型对世界的理解。

推理

在前面的步骤中,我们执行预处理和训练,实际上已经完成了许多文本视为建模的工作。对我们来说,建模与其说是一个模型的开发,不如说是一个经过训练的模型的应用,以执行对系统行为建模的推理。毕竟,我们建立预测性机器学习模型的根本原因通常是为了模拟在一组特定环境下会发生什么,这些环境要么尚未发生,要么已经发生,但我们不知道实际结果。

关于这篇文章

这是一个链接系列的第六篇文章,旨在提供数据科学过程入门的简单介绍。你可以在这里找到简介,在这里找到上一篇文章,在这里找到系列文章

波兰语仇恨言论建模:特定领域嵌入的重要性

原文:https://towardsdatascience.com/modelling-hate-speech-in-polish-importance-of-domain-specific-embeddings-206a02fb3a3b?source=collection_archive---------57-----------------------

机器学习和社交媒体

我们通过使用具有特定领域嵌入的 FastText 与 RoBERTa 和 ULMFiT 竞争

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

图片来自安德鲁·亨特Unsplash

在 Sotrender,我们通过将内部构建的机器学习模型应用于社交媒体,利用人工智能增强数字营销。我们最近的挑战是检测波兰信息中的仇恨言论。下面你可以看到我们是如何解决这个问题的,以及我们所获得的结果。

仇恨言论的问题

在互联网时代,讨论正在各种交流平台上在线进行,而不是面对面。缺少我们信息的直接接收者会给人一种欺骗性的匿名感。这让我们可以更自由地表达自己,但另一方面,忽略了我们的文本会被另一个人阅读的事实。这也被称为在线去抑制效应。此外,人们不会被迫为他们大部分时间在网上发布的言论负责。这些观点导致了网上讨论中明显的仇恨

由于最近在自然语言处理(NLP)方面的进步,越来越多的有害信息可以被自动检测出来(T3)。然而,主流的自然语言处理研究只集中在一个小的子集,要么是常用语言,要么是人口众多的语言,如英语或汉语(参见[1]对自然语言处理中语言多样性的综合研究)。这在 NLP 模型的质量(以及检测有害内容的能力)之间产生了明显的差异,这取决于它们所针对的语言。虽然最近的一些研究旨在通过开发多语言模型来弥合这一差距,大多数时候,那些针对单一语言的研究呈现出更好的语言理解能力。幸运的是,一些新的可能性正在出现,以支持对代表性不足的语言的研究。

在这篇文章中,我们将关注波兰语言中的仇恨言论检测问题。我们将从描述最近创建的数据集开始,该数据集可用作仇恨言论检测方法的基准。然后,我们将讨论我们基于快速简单的深度学习模型的方法。我们将我们的方法与来自 PolEval 竞赛和 KLEJ 基准的提交文件进行了比较。与 PolEval 的结果相比,所提出的模型实现了显著的改进,并且与 KLEJ 的基于变压器的模型相当,同时在概念上更简单并且需要更少的计算资源。我们方法的优势展示了特定领域文本嵌入的重要性,以及非常适合手头任务的神经模块的使用

pole val——seme val 的波兰对应物

PolEval 是专门为波兰语设计的 NLP 工具的评估活动。从 2017 年开始,他们每年举办各种语言理解任务的比赛,如机器翻译命名实体识别。2019 年,其中一项任务是解决从公共论坛上的讨论中收集的信息中检测有害内容的问题。也就是说,分为两部分的"自动网络欺凌检测"任务旨在区分无害和有害信息,或将有害信息进一步分类为网络欺凌或仇恨言论。虽然乍一看,网络欺凌和仇恨言论之间的区别可能并不明显,但这项任务根据特定有害行为的目标将它们区分开来:在网络欺凌中,它是针对个人的,而在仇恨言论中是针对一个群体或实体的。更详细的定义在任务附带的一篇论文中讨论[2]。

数据描述

用于该任务的数据集由从公开的 Twitter 讨论中收集的 tweets 组成。这些推文经过了最低限度的预处理,因此不包含私人身份信息。除此之外,它们的原始格式没有改变。这意味着,它们可以包含表情符号、表情符号、标签、HTTP URLs 和拼写错误等噪音。虽然 URL 通常不包含关于信息危害性的有用信息,但是表情符号、表情符号和标签可以用来加强文本内容或表示讽刺。数据集中的一个示例如下所示:

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

检测有害信息的另一个挑战来自讨论的主题。数据集中的大量消息与政治相关。一般来说,人们可以怀疑在线平台上发布的关于政治的信息可能包含更多的负面情绪。基于这种数据训练的模型可以学习不期望的偏见,并且认为关于该主题的所有消息更可能是有害的。一般来说,来自数据集的推文指的是政治、体育和新闻(更详细的描述可以在[2]中找到)。

最后,从机器学习(ML)模型的角度来看,数据集可能被认为是具有挑战性的。它包含大约 11k 条 tweet(10k 用于训练集,1k 用于测试),与其他 NLP 任务相比,这是一个相当小的数目。此外,类别分布严重不平衡,因为大多数文本是无害的(见图 1)。

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

**图一。**数据集的训练和测试分裂中的类分布。数据集中的大多数消息是无害的。训练拆分仅包含 8.5%的有害消息,而测试包含 13.4%。

我们的方法

大多数最先进的 NLP 模型都是在结构良好的文档上预先训练的,例如来自维基百科的文章,这允许我们建立一个丰富而准确的语言模型。然而,这些预先训练过的模型可能不一定知道如何处理特殊的符号,如新的表情符号、标签或社交媒体传播专用的全新短语,他们在没有接受过这些训练。不同语言风格、缩写、俚语、混合不同语言的单词或简单的打字错误或语法错误的存在将进一步测试模型概括分布外数据的能力。然而,处理词汇表外键的令牌嵌入方法可以部分解决这个问题。

此外,这些型号中的大多数都使用了变压器【3】架构。虽然最近它已经成为大多数 NLP 问题的标准选择,但与 FastText [4,5]等更简单的方法相比,它需要大量的计算资源。在为生产环境开发模型时,必须考虑这一特性。基于 Transformer 的模型的推理时间,即使部署在能够访问 GPU 的实例上,也可能导致过高的延迟。此外,由于高昂的财务成本,用 GPU 部署多个副本可能是不可能的。

牢记这两个方面,我们的方法基于低延迟令牌嵌入模型,专门针对来自社交媒体的数据进行训练,并将其与简单有效的分类头相结合。给定一条消息,我们从简单的预处理步骤开始:

  • 将所有字符改为小写,
  • 删除空白字符(制表符、换行符等。),
  • 丢弃 URL,
  • 将表情符号、表情符号和标签编码为特殊符号,这些符号出现在我们的词汇中(“😃”-> “[SMILEY_HAPPY]”)。

结果是一个干净的消息,然后被分割成令牌,这些令牌可以使用令牌嵌入模型编码为向量。

我们没有使用已经预先训练好的令牌嵌入模型,而是决定专门在我们自己的社交媒体数据集上从头训练一个新的模型。大部分数据来自脸书几年来收集的帖子和评论。此外,它还包含来自其他社交媒体平台的文本,如 Instagram 或 YouTube。我们使用另一种语言检测模型只选择了波兰语文本。总的来说,该数据集包含 10 亿个单词,其中超过 300 万个是唯一的。这些文本经过了最少的预处理,主要是删除 URL,将所有表情符号和表情符号编码为特殊的符号,并将其包含在我们的词汇中。然而,我们没有纠正任何错别字。

虽然最近的工作主要集中在基于 Transformer 的架构上,但是我们选择了 FastText 作为令牌嵌入模型,原因有几个。首先,快速的推理时间和较低的计算要求使得该模型易于部署。其次,FastText 使用字符级信息,这使得该方法非常适合于形态学丰富的语言,如波兰语。

值得指出的是,从头开始创建模型需要大量的时间,不仅是为了训练(我们在一天内完成了训练),也是为了准备数据集。尽管如此,在 Sotrender 中,我们处理的数据主要来自社交媒体,因此我们能够在其他 NLP 任务中使用该模型,如情感分析。此外,训练基于 Transformer 的模型(如 BERT)通常不是您的首选,因为它对硬件的要求很高,甚至比 FastText 花费更多的时间,因此非常昂贵。我们认为,在实践中,最好从简单的解决方案开始,只有在必要时才逐渐增加复杂性。

在这一点上,重要的是观察到,在许多情况下,检测仇恨言论的任务可以通过搜索句子中有害的单词序列(标记)来解决。通常,识别这样的序列就足够了,不需要分析整个信息的上下文。受这一观察的激励,我们在令牌嵌入模型之后直接采用了一维卷积神经网络(CNN)。包含在 CNN 架构中的平移不变性允许我们精确地处理这样的记号序列。此外,通过考虑具有固定宽度(卷积核的大小)的记号列表的所有这样的子阵列,我们能够创建整个句子的表示。类似的方法可以在其他一些作品[6,7]中找到,甚至可以在单词袋(BoW)模型中找到,该模型考虑了 n-grams 的令牌。图 2 展示了如何将 1D 卷积应用于一系列令牌嵌入。

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

图二。 1D 卷积神经网络通过在标记序列上滑动固定宽度的窗口来处理编码的句子。该窗口能够捕捉连续记号的模式,而与它们在句子中的位置无关。为了捕捉不同的模式,每个 CNN 层由许多这样的窗口组成,由给定层的深度决定。

虽然 CNN 大多是从计算机视觉问题中得知的,我们的方法证明了它们同样适用于包括文本分类在内的序列处理任务,同时比递归模块的计算要求更低。我们还尝试用其他在序列处理方面表现突出的架构来替代 CNN,包括长短期记忆(LSTM)或门控循环单元(GRU),但最好的结果是使用具有池层的单层 CNN。

总之,使用具有池层的浅 1 维 CNN 来变换令牌嵌入,并使用跟随有 softmax 的学习线性变换来投影到类分布中。此外,我们在倒数第二层使用 l2 正则化和丢弃。完整的架构如图 3 所示。

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

**图三。**我们完整的文本处理管道,将句子转换成预测的类别分布。

在训练期间,令牌嵌入模型的权重被冻结,并且网络的剩余可学习参数被优化。在大多数实验中,我们使用验证数据集(使用分层抽样从训练分裂中提取)来提前停止和选择超参数,除非另有说明。此外,我们在训练期间应用简单的数据扩充,这将打乱和删除随机选择的令牌。我们计划在未来的文章中对测试过的增强方法进行全面的回顾。

结果

我们分两步对我们的方法进行了评估。首先,我们将其与提交给 PolEval2019 Task 6 的最佳模型进行了比较。该比较包括该任务的二进制(任务 6.1)和多类(任务 6.2)版本。接下来,我们查看了使用 KLEJ 基准[8]中的 CBD 任务评估的大型变压器模型的结果。由于不均衡的类别分布,对于二元分类任务,使用 F1 分数来比较模型,并且在多类别分类的情况下,使用微观 F1 和宏观 F1 来比较模型。

PolEval2019 任务 6

在这两项任务中,我们将我们的模型与五个最佳竞争对手进行了比较。有趣的是,提交的材料包含一些不同的方法。对于任务 6.1,通过以下方法获得了最佳结果(表 1):

  • n-waves UML fit[9]——ul fit 架构的扩展,适用于使用子词标记化的形态丰富的语言,
  • Przetak [10] —一种识别有毒单词形式并使用对字符 5-grams 单词的逻辑回归来聚集它们的方法,
  • ULMFiT+SP+BA — ULMFiT 与句子成分和分支注意相结合,
  • 集成 spacy + tpot + BERT [11] —由来自 spacy 和 BERT 的分类器组成的集成,结合使用 tpot 库自动创建的文本处理管道,
  • 系综空间+ tpot [11] —同上,除了使用 ULMFiT 代替 BERT。

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

**表 1。**将结果与提交给 PolEval2019 任务 6.1 的模型进行比较。我们的模型被命名为软渲染

任务 6.2(表 2)中的最佳执行方法还包括:

  • 模型 1-svm [12] —在表征的 TF-IDF 表示上训练的支持向量机,
  • fasttext [13] —基于在“Narodowy Korpus jzyka Polskiego”(NKJP)上预训练的 fasttext 单词嵌入的模型,
  • model3-flair [12] —该方法依赖于快速文本单词嵌入与来自 flair 的前向和后向字符级语言模型的组合,其形成双向 GRU 网络的输入,
  • SCWAD-CB [14] —基于特征组合的多层感知器,包括激光嵌入、Morfeusz 限定词、基于外部词汇和字符 n-grams 提取的粗俗和攻击性词语。

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

**表二。**将结果与提交至 PolEval2019 任务 6.2 的模型进行比较。

正如在两个表中所看到的,我们的方法大大超过了其他报道的结果。重要的是要记住,我们的模型不是基于复杂的功能,如[10]或[14],也不依赖于大架构,如[9]和[11]。这种明显的改进来自于使用领域特定的嵌入,这些嵌入是在类似于模型化的任务/问题的数据上训练的。

KLEJ 基准

为了进一步证明我们模型的优势,我们将它与 KLEJ 基准[8]中评估的基于变压器的方法进行了比较。在许多任务中,它包括网络欺凌检测(CBD),它使用与 PolEval2019 的任务 6.1 相同的数据集。出于公平比较的目的,我们遵循[15]中的设置,并在整个训练分割上训练模型。

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

**表 3。**将结果与提交给 KLEJ 基准的模型进行比较【8】。有关各型号的详细信息,请参考 KLEJ 排行榜

从表中可以看出,虽然我们没有达到排行榜的首位,但我们取得了不相上下的成绩。我们的车型排名第四,超越了基于伯特、罗伯塔或 XLM 的其他车型。

结论

在本文中,我们已经展示了实现复杂的架构或者增加模型参数的数量并不是成功的唯一途径。我们的方法认为,另一个关键因素是使用特定领域的归纳偏见。在我们的例子中,这些偏差主要存在于令牌嵌入模块中,但也存在于 CNN 中,CNN 非常适合于仇恨言论检测的问题。

由于我们方法的简单性,我们能够成功地将我们的模型部署到生产环境中。推理过程可以在没有 GPU 的情况下执行,并且只需要一个 CPU 和不到 3GB 的 RAM。这产生了易于水平扩展的低成本解决方案。如果您对我们如何部署我们的模型感兴趣,请在评论中提出问题,我们也会尽力准备一篇关于它的文章。

从这篇文章中得到的关键观察是,认识到即使在变形金刚的世界里,锤子也可以被螺丝刀代替。

参考文献

[1] P. Joshi 等人,NLP 世界中语言多样性和包容性的状况和命运 (2020),arXiv 预印本 arXiv:2004.09095

[2] M. E. Ptaszynski 和 F. Masui 编辑。,自动网络欺凌检测:新兴研究和机遇:新兴研究和机遇 (2018),IGI 全球 2018

[3] A. Vaswani 等人,注意力是你所需要的全部 (2017),神经信息处理系统进展 2017

[4] P. Bojanowski 等,用子词信息丰富词向量 (2017),计算语言学协会会刊 5(2017):135–146

[5] A. Joulin 等人,高效文本分类的锦囊妙计 (2016),arXiv 预印本 arXiv:1607.01759

[6]b . gamb CK 和 U. K. Sikdar,使用卷积神经网络对仇恨言论进行分类 (2017),第一届在线辱骂性语言研讨会会议录

[7] S. Bai,J. Z. Kolter 和 V. Koltun,用于序列建模的一般卷积和递归网络的经验评估 (2018),arXiv 预印本 arXiv:1803.01271

[8] P. Rybak 等人, KLEJ:波兰语理解综合基准 (2020),arXiv 预印本 arXiv:2005.00630

[9] P. Czapla 等人,波兰仇恨言论检测通用语言模型微调 (2019),PolEval 2019 年研讨会会议录:149

[10] M. Ciura, Przetak:网络上的杂草越来越少 (2019),PolEval 2019 研讨会会议录:127

[11] R. Korzeniowski 等人,利用无监督预训练和自动化特征工程进行波兰语低资源仇恨言论检测 (2019),arXiv 预印本 arXiv:1906.09325

[12] M. Biesek,波兰语自动网络欺凌检测中传统机器学习方法和深度学习模型的比较 (2019),PolEval 2019 研讨会论文集:121

[13] K. Wróbel,对波兰推文进行自动网络欺凌检测 (2019)

[14] K. Krasnowska-Kieras 和 A. Wróblewska,用于检测网络欺凌的简单神经网络 (2019),PolEval 2019 研讨会会议录:161

[15] S. Dadas,m . perekiewicz 和 r . powiata,大规模预训练基于波兰语转换器的语言模型 (2020),arXiv 预印本 arXiv:2006.04229

建模回归树

原文:https://towardsdatascience.com/modelling-regression-trees-b376e959d02e?source=collection_archive---------11-----------------------

如何编写这个经典的机器学习算法(Python)

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

🇨🇭·克劳迪奥·施瓦茨| @purzlbaum 在 Unsplash 上拍摄的照片

决策树可能是最流行的机器学习算法之一。在我的帖子 " 决策树完全指南 s" 中,我详细描述了 DT:它们在现实生活中的应用,不同的 DT 类型和算法,以及它们的优缺点。我已经详细介绍了如何编写分类树,现在轮到回归树了。

回归树使用数字目标变量。与目标变量是定性的分类树不同,回归树用于预测连续输出变量。如果您想要预测诸如医疗成功的概率、金融股票的未来价格或给定人群的工资等事情,您可以使用此算法。让我们看一个用 Python 实现的例子。

问题是

波士顿住房数据集由美国波士顿不同地方的房价组成。除了价格之外,该数据集还提供了犯罪水平、城镇非零售商业区、房屋所有者的年龄以及其他属性等信息。

名为“MEDV”的变量表示房价,是目标变量。其余的变量是我们预测房子价值的预测因素。

台阶

您可以通过处理更简单的子步骤来降低构建 DTs 的复杂性:DT 中的每个单独的子例程都将连接到其他子例程以增加复杂性,这种构造将让您获得更健壮的模型,更易于维护和改进。现在,让我们用 Python 构建一棵回归树(特殊类型的 DT)。

加载数据并描述数据集

加载数据文件是最简单的部分。问题(也是最耗时的部分)通常是指数据准备过程:设置正确的数据格式、处理缺失值和异常值、消除重复值等。

在加载数据之前,我们将导入必要的库:

import pandas as pd
from pandas_datareader import data
import numpy as np
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn import metrics
from sklearn.metrics import r2_score

现在,我们加载数据集并将其转换为熊猫数据帧:

boston = datasets.load_boston()
df = pd.DataFrame(boston.data)

并将列命名为:

df.columns = boston.feature_names
df[‘MEDV’] = boston.target

首先理解数据集并描述它:

print(boston.DESCR)df.info()

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

不错:506 条记录,14 个数字变量,没有缺失值。我们不需要预处理数据,我们已经准备好建模。

选择特征和目标变量

您需要将给定的列分成两种类型的变量:因变量(或目标变量)和自变量(或特征变量)。在我们的例子中,变量“MEDV”(自有住房的中值)是我们试图预测的。

X = df.iloc[:,0:13].copy()
y = df.iloc[:,13].copy()

分割数据集

要了解模型性能,将数据集分为定型集和测试集是一个好策略。通过将数据集分成两个独立的集合,我们可以使用一个集合进行训练,使用另一个集合进行测试。

  • **训练集:**这些数据用来建立你的模型。例如使用 CART 算法来创建决策树。
  • **测试集:**该数据用于查看模型在看不见的数据上的表现,就像在现实世界中一样。在您想要测试您的模型以评估性能之前,这些数据应该是完全不可见的。

接下来,我们将数据集分成 70%训练和 30%测试。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)

建立 DT 模型并微调

构建 DT 就像这样简单:

rt = DecisionTreeRegressor(criterion = ‘mse’, max_depth=5)

在这种情况下,我们只定义了分裂标准(选择均方误差)和一个超参数(树的最大深度)。定义模型架构的参数被称为超参数,因此,搜索理想模型架构(最大化模型性能的架构)的过程被称为超参数调整*。* A 超参数是在学习过程开始前就设定好值的参数,它们不能直接从数据中训练出来。

您可以通过调用模型来查看可以优化的其余超参数:

rt

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

模型可以有许多超参数,并且有不同的策略来寻找参数的最佳组合。你可以在这个链接上看看其中的一些。

列车 DT 模型

将模型拟合到训练数据代表了建模过程的训练部分。在模型定型后,可以使用预测方法调用来进行预测:

model_r = rt.fit(X_train, y_train)

测试 DT 模型

测试数据集是独立于训练数据集的数据集。该测试数据集是您的模型的未知数据集,有助于您对其进行概化:

y_pred = model_r.predict(X_test)

设想

DTs 最大的优势之一是它们的可解释性。可视化 DTs 不仅是理解模型的有效方法,也是传达模型工作原理的有效方法:

from sklearn import tree
import graphviz
dot_data = tree.export_graphviz(rt, feature_names=list(X), class_names=sorted(y.unique()), filled=True)
graphviz.Source(dot_data)

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

变量“LSTAT”似乎对定义回归树的划分至关重要。我们将在计算要素重要性后检查这一点。

评估绩效

模型的质量与其预测值与实际值的匹配程度有关。评估你的机器学习算法是任何项目必不可少的一部分:你如何衡量它的成功,你什么时候知道它不应该再改进了?不同的机器学习算法有不同的评估指标,所以让我们提到一些回归问题的主要评估指标:

平均绝对误差(MAE)

是测试集中所有实例的单个预测误差绝对值的平均值**。它告诉我们平均预期误差有多大。**

print(‘Mean Absolute Error:’, metrics.mean_absolute_error(y_test, y_pred))

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

均方误差

是测试集中所有实例的预测误差平方的平均值。因为 MSE 是平方的,所以它的单位与原始输出的单位不匹配,而且因为我们正在平方差值,所以 MSE 几乎总是大于 MAE:因此我们不能直接比较 MAE 和 MSE。

print(‘Mean Squared Error:’, metrics.mean_squared_error(y_test, y_pred))

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

MSE 方程中平方项的影响在我们的数据中存在异常值时最为明显:虽然 MAE 中的每个残差按比例贡献给总误差,但 MSE 中的误差以二次方的方式增长。这最终意味着,我们数据中的异常值将导致 MSE 中比 MAE 中更高的总误差,并且该模型将因做出与相应实际值相差很大的预测而受到更多惩罚。

均方根误差(RMSE)

是所有误差的平方的平均值的平方根。通过在计算平均值之前对误差求平方,然后取平均值的平方根,我们得到了一个误差大小的度量,该度量对较大但不常见的误差给予了比平均值更大的权重。我们还可以比较 RMSE 和梅,以确定预测是否包含大量但不常见的误差:RMSE 和梅之间的差异越大,误差大小越不一致。

print(‘Root Mean Squared Error:’, np.sqrt(metrics.mean_squared_error(y_test, y_pred)))

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

R 平方得分(R2)

用百分比解释由于特征变量的变化而引起的响应变量的变化量。 R 的平方可以取 0 到 1 之间的任何值,尽管它提供了一些关于回归模型的有用见解,但是您不应该只依赖这个度量来评估您的模型。

print(‘R Squared Score is:’, r2_score(y_test, y_pred))

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

对 R 平方最常见的解释是回归模型与观察数据的拟合程度。就像我们的例子一样,R 的平方为 0.74 表明 74%的数据符合回归模型。虽然较高的 R 平方表示模型更适合,但高度量值并不总是适合回归模型:统计度量值的质量取决于许多因素,例如模型中使用的变量的性质、变量的度量单位以及应用的数据转换。

特征重要性

另一个关键指标包括为预测模型的输入特征分配分数,表明每个特征在进行预测时的相对重要性。特征重要性提供对数据、模型的洞察,并代表降维和特征选择的基础,这可以提高预测模型的性能。越多的属性用于 DT 的关键决策,其相对重要性就越高。

for importance, name in sorted(zip(rt.feature_importances_, X_train.columns),reverse=True):
 print (name, importance)

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

如可视化中突出显示的,变量“LSTAT”相对于其他变量具有更高的重要性(是模型的主要特征)。让我们在图上看一下:

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

特征“LSTAT”和“RM”占进行预测的重要性的 80%以上。

我们只能将我们模型的误差指标与竞争模型的误差指标进行比较(例如,2 个不同模型的 R 平方得分),尽管这些指标提供了关于模型性能的宝贵见解,但请始终记住:

仅仅因为一个预测在过去是准确的,并不意味着它在未来也是准确的。

最后的想法

我们在建模过程中已经涵盖了几个步骤,其中每一个都是独立的学科:探索性数据分析、特征工程或超参数调整都是任何机器学习模型的广泛而复杂的方面。你应该考虑更深入地研究那些学科。

与其他算法相比,决策树的一个重要方面是它划分数据空间的方式。如果您选择用线性回归来求解波士顿房价预测,您会看到如下图表:

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

线性回归将搜索目标与其预测值之间的线性关系。在这个例子中,两个变量(“MEDV”和“RM”)似乎是线性相关的,这就是为什么这种方法可能工作得相对较好,但现实往往显示非线性关系。让我们看看回归树如何映射目标和预测值之间的相同关系:

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

在此示例中,使用 MSE 作为分区标准且 max_depth 为 5 的回归树以完全不同的方式划分数据空间,从而确定线性回归无法拟合的关系。

决策树划分数据空间以优化给定标准的方式不仅取决于标准本身(例如 MSE 或 MAE 作为划分标准),还取决于所有超参数的设置。超参数优化定义了决策树的工作方式,并最终决定了它的性能。一些超参数会严重影响模型的性能,找到它们的正确级别对于达到最佳性能至关重要。在下面的示例中,您可以看到超参数 max_depth 在设置为 0 到 10 之间时如何对回归树的 R 平方得分产生巨大影响,但在 10 以上,您选择的任何级别都不会对其产生影响:

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

为了克服你试图为你的 DT 找到“完美的”超参数水平而使你的模型过度拟合的事实,你应该考虑探索集合方法。集成方法将几个 DTs 结合起来,产生比单个 DTs 更好的预测性能。系综模型背后的主要原理是一群弱学习者聚集在一起形成强学习者,显著提高单个 DT 的性能。它们用于减少模型的方差和偏差,并改进预测。既然你已经看到了决策树是如何工作的,我建议你继续使用集合方法,比如打包或推进

对这些话题感兴趣?在Linkedin Twitter 上关注我

用 CatBoost 和 NODE 对表格数据建模

原文:https://towardsdatascience.com/modelling-tabular-data-with-catboost-and-node-929bfbaaeb08?source=collection_archive---------15-----------------------

俄罗斯在线搜索公司 Yandex 的 CatBoost 快速易用,但最近该公司的研究人员发布了一个新的基于神经网络的包 NODE,他们声称该包优于 CatBoost 和所有其他梯度提升方法。这可能是真的吗?让我们了解一下如何同时使用 CatBoost 和 NODE!

这篇博文是写给谁的?

虽然我写这篇博文是为了那些对机器学习,尤其是表格数据感兴趣的人,但是如果你熟悉 Python 和 scikit-learn 库,并且想了解代码,那么这篇博文会很有帮助。如果你不是,希望你会发现理论和概念部分很有趣!

CatBoost 简介

CatBoost 是我用来建模表格数据的软件包。它是梯度增强决策树的一种实现,只做了一些调整,与 xgboostLightGBM 略有不同。它适用于分类和回归问题。

CatBoost 的一些优点:

  • 它处理电气特征(明白吗?)开箱即用,所以不需要担心如何对它们进行编码。
  • 它通常只需要很少的参数调整。
  • 它避免了其他方法可能遭受的某些微妙类型的数据泄漏。
  • 它很快,如果你想让它跑得更快,可以在 GPU 上运行。

对我来说,这些因素使得 CatBoost 成为当我需要分析新的表格数据集时首先要做的事情。

CatBoost 的技术细节

如果只是想用 CatBoost 就跳过这一节!

在更技术性的层面上,关于 CatBoost 是如何实现的,有一些有趣的事情。如果你对细节感兴趣,我强烈推荐论文 Catboost:具有分类特征的无偏增强。我只想强调两件事。

  1. 在这篇论文中,作者表明,标准梯度推进算法受到模型迭代拟合方式导致的微妙类型的数据泄漏的影响。同样,对分类特征进行数字编码的最有效方法(如目标编码)也容易出现数据泄漏和过拟合。为了避免这种泄漏,CatBoost 引入了一个人工时间线,训练示例根据该时间线到达,以便在计算统计数据时只能使用“以前看到的”示例。
  2. CatBoost 其实不用常规决策树,而是不经意决策树。在这些树中,在树的每一层,相同的特征和相同的分裂标准被到处使用!这听起来很奇怪,但是有一些很好的特性。我们来看看这是什么意思。

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

左: 浑然不觉决策树。每个级别都有相同的拆分。 右: 常规决策树。任何特征或分割点都可以出现在每个级别上。

在普通决策树中,要分割的特征和截止值都取决于到目前为止您在树中选择的路径。这是有意义的,因为我们可以使用我们已经拥有的信息来决定下一个最有价值的问题(就像在“20 个问题”游戏中一样)。对于遗忘决策树,历史并不重要;不管怎样,我们都提出同样的问题。这些树被称为“健忘的”,因为它们总是“忘记”以前发生过的事情。

这为什么有用?不经意决策树的一个很好的特性是,一个例子可以很快被分类或评分——它总是提出相同的 N 个二元问题(其中 N 是树的深度)。对于许多示例,这可以很容易地并行完成。这是 CatBoost 速度快的一个原因。另一件要记住的事情是,我们在这里处理的是一个树集合。作为一个独立的算法,遗忘决策树可能不会工作得很好,但树集成的想法是,弱学习者的联盟通常工作得很好,因为错误和偏见被“淘汰”。通常,弱学习者是一个标准的决策树,这里它是一个更弱的东西,即不经意决策树。CatBoost 的作者认为,这种特殊的弱基础学习者很适合泛化。

安装 CatBoost

虽然安装 CatBoost 应该是一件简单的事情

pip install catboost

在 Mac 电脑上,我有时会遇到这方面的问题。在 Linux 系统上,比如我现在输入的 Ubuntu 系统,或者在 Google Colaboratory 上,它应该“正常工作”。如果你在安装时一直有问题,考虑使用 Docker 镜像,例如

docker pull yandex/tutorial-catboost-clickhouse
docker run -it yandex/tutorial-catboost-clickhouse

在数据集上使用 CatBoost

链接到代码为的 Colab 笔记本

让我们看看如何在表格数据集上使用 CatBoost。我们从下载成人/人口普查收入数据集的轻度预处理版本开始,在下文中,假设该数据集位于 datasets/adult.csv 中。我选择该数据集是因为它混合了分类和数字特征,在数万个示例中具有良好的可管理大小,并且没有太多的特征。它经常被用来举例说明算法,例如在谷歌的假设工具和许多其他地方。

成人人口普查数据集在 Colab 上有“年龄”、“工作阶级”、“教育”、“教育人数”、“婚姻状况”、“职业”、“关系”、“种族”、“性别”、“资本收益”、“资本损失”、“每周小时数”、“本国”和“T6”等列,但我将在此复制这些列以供参考。CatBoost 需要知道哪些特征是分类的,然后自动处理它们。在这个代码片段中,我还使用 5 重(分层)交叉验证来估计预测精度。

我们从运行这个(没有超参数优化的 CatBoost)中得到的是 85%到 86%之间的平均准确率。在我最后一次运行中,我得到了大约 85.7%。

如果我们想要尝试优化超参数,我们可以使用 hyperopt(如果您没有它,请使用 pip install hyperopt 安装它)。为了使用它,您需要定义一个 hyperopt 试图最小化的函数。我们将尝试优化这里的精度。也许对日志丢失进行优化会更好,但这是留给读者的练习;)

from catboost import CatBoostClassifier, Pool
from hyperopt import fmin, hp, tpe
import pandas as pd
from sklearn.model_selection import StratifiedKFolddf = pd.read_csv('https://docs.google.com/uc?' + 
                 'id=10eFO2rVlsQBUffn0b7UCAp28n0mkLCy7&' + 
                 'export=download')
labels = df.pop('<=50K')categorical_names = ['workclass', 'education', 'marital-status',
                     'occupation', 'relationship', 'race',
                     'sex', 'native-country']  
categoricals = [df.columns.get_loc(i) for i in categorical_names]nfolds = 5
skf = StratifiedKFold(n_splits=nfolds, shuffle=True)
acc = []for train_index, test_index in skf.split(df, labels):
  X_train, X_test = df.iloc[train_index].copy(), \
                    df.iloc[test_index].copy()
  y_train, y_test = labels.iloc[train_index], \
                    labels.iloc[test_index]
  train_pool = Pool(X_train, y_train, cat_features = categoricals)
  test_pool = Pool(X_test, y_test, cat_features = categoricals)
  model = CatBoostClassifier(iterations=100,
                             depth=8,
                             learning_rate=1,
                             loss_function='MultiClass') 
  model.fit(train_pool)
  predictions = model.predict(test_pool)
  accuracy = sum(predictions.squeeze() == y_test) / len(predictions)
  acc.append(accuracy)mean_acc = sum(acc) / nfolds
print(f'Mean accuracy based on {nfolds} folds: {mean_acc:.3f}')
print(acc)

要优化的主要参数可能是迭代次数、学习速率和树深度。还有许多与过拟合相关的其他参数,例如提前停止轮次等。请自行探索!

当我最后一次运行这段代码时,它花了 5 个多小时,但平均准确率为 87.3%,这与我在尝试 Auger.ai AutoML 平台时获得的最好结果相当。

健全性检查:逻辑回归

# Optimize between 10 and 1000 iterations and depth between 2 and 12search_space = {'iterations': hp.quniform('iterations', 10, 1000, 10),
                'depth': hp.quniform('depth', 2, 12, 1),
                'lr': hp.uniform('lr', 0.01, 1)
               }def opt_fn(search_space): nfolds = 5
  skf = StratifiedKFold(n_splits=nfolds, shuffle=True)
  acc = [] for train_index, test_index in skf.split(df, labels):
    X_train, X_test = df.iloc[train_index].copy(), \
                      df.iloc[test_index].copy()
    y_train, y_test = labels.iloc[train_index], \
                      labels.iloc[test_index]
    train_pool = Pool(X_train, y_train, cat_features = categoricals)
    test_pool = Pool(X_test, y_test, cat_features = categoricals) model = CatBoostClassifier(iterations=search_space['iterations'],
                             depth=search_space['depth'],
                             learning_rate=search_space['lr'],
                             loss_function='MultiClass',
                             od_type='Iter') model.fit(train_pool, logging_level='Silent')
    predictions = model.predict(test_pool)
    accuracy = sum(predictions.squeeze() == y_test) / len(predictions)
    acc.append(accuracy) mean_acc = sum(acc) / nfolds
  return -1*mean_accbest = fmin(fn=opt_fn, 
            space=search_space, 
            algo=tpe.suggest, 
            max_evals=100)

在这一点上,我们应该问自己是否真的需要这些新奇的方法。一个好的旧逻辑回归在开箱即用和超参数优化后会有怎样的表现?

为了简洁起见,我在这里省略了复制代码,但它在和之前一样的 Colab 笔记本中是可用的。逻辑回归实现的一个细节是,它不像 CatBoost 那样处理开箱即用的分类变量,所以我决定使用目标编码对它们进行编码,特别是留一目标编码,这是 NODE 中采用的方法,与 CatBoost 中发生的情况非常接近,但不完全相同。

长话短说,使用这种编码类型的未调整的逻辑回归产生大约 80%的准确性,在超参数调整后大约 81%(在我最近的运行中是 80.7%)。这里,一个有趣的替代方法是尝试自动化预处理库,如 vtreatAutomunge ,但我将把它们留到下一篇博文中!

盘点

在尝试 NODE 之前,到目前为止我们有什么?

未调整的逻辑回归:80.0%

逻辑回归,调整率:80.7%

  • CatBoost,未调整:85.7%
  • 催化增强,调优:87.2%
  • 节点:神经不经意决策集成
  • Yandex 研究人员最近的一份手稿描述了一个有趣的神经网络版本的 CatBoost,或者至少是一个神经网络采用了不经意决策树集成(如果你想提醒自己“不经意”在这里是什么意思,请参见上面的技术部分。)这种体系结构称为节点,可用于分类或回归。

摘要中的一项声明写道:“通过在大量表格数据集上与领先的 GBDT 软件包进行广泛的实验比较,我们展示了所提出的节点架构的优势,它在大多数任务上优于竞争对手。”这自然激起了我的兴趣。这个工具会比 CatBoost 更好吗?

NODE 是如何工作的?

你应该去报纸上看完整的故事,但是一些相关的细节是:

entmax 激活函数被用作常规决策树中分裂的软版本。正如论文所说,“entmax 能够产生稀疏的概率分布,其中大多数概率恰好等于 0。在这项工作中,我们认为,在我们的模型中,entmax 也是一个适当的归纳偏差,它允许在内部树节点中进行可微分的分裂决策构建。直观地说,entmax 可以根据数据特征的一个小子集(最多一个,如在经典决策树中)学习拆分决策,避免来自其他人的不良影响。entmax 函数允许神经网络模拟决策树类型的系统,同时保持模型可微分(权重可以基于梯度更新)。

作者提出了一种新类型的层,即“节点层”,可以在神经网络中使用(他们的实现是在 PyTorch 中)。一个节点层代表一个树集合。

  • 几个节点层可以堆叠,产生一个层次模型,其中输入通过一个树集合一次馈入。输入表示的连续连接可以用于给出一个模型,该模型让人想起用于图像处理的流行的 DenseNet 模型,只是专门用于表格数据。
  • 节点模型的参数包括:
  • 学习率(论文中总是 0.001)

节点层数( k

  • 每层树的数量( m )
  • 每层树的深度( d )
  • 节点与树集合有什么关系?
  • 为了感受一下这种神经网络架构和决策树集合之间的相似性,这里复制了图 1。

节点层如何与决策树相关联。

应该如何选择参数?

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

手稿中没有太多的指导;作者建议使用超参数优化。他们确实提到他们在以下领域进行了优化:

层数:{2,4,8}

总树数:{1024,2048}

  • 树深度:{6,8}
  • 树输出尺寸:{2,3}
  • 在我的代码中,我不进行网格搜索,而是让 hyperopt 在特定范围内采样值。我认为(这可能是错误的)每一层都代表一个树集合(比如说 CatBoost 的一个实例)。对于您添加的每一层,您可能会添加一些表示能力,但是您也会使模型变得更难训练,并可能有过度拟合的风险。总树数似乎大致类似于 CatBoost/xgboost/random 森林中的树数,并且具有相同的权衡:如果有许多树,您可以表达更复杂的函数,但是模型将需要更长的时间来训练,并且有过度拟合的风险。树的深度也有同样的权衡。至于输出维度,坦白说,我不太明白为什么是参数。看论文,好像回归应该等于 1,分类应该等于类数。
  • 如何使用节点?

作者们已经在 GitHub 上发布了代码。他们不提供命令行界面,而是建议用户在提供的 Jupyter 笔记本上运行他们的模型。这些笔记本中提供了一个分类示例和一个回归示例。

repo README 页面还强烈建议使用 GPU 来训练节点模型。(这是有利于 CatBoost 的一个因素。)

我准备了一个合作笔记本,里面有一些关于如何在节点上运行分类以及如何用 hyperopt 优化超参数的示例代码。

现在请移至 合作笔记本 继续关注!

这里我将只强调代码的一些部分。

改编代码的一般问题

我在改编作者的代码时遇到的问题主要与数据类型有关。重要的是,输入数据集(X_train 和 X_val)是 float32 格式的数组(numpy 或 torch );不是 float64 或 float 和 int 的混合。标签需要编码为长( int64 )用于分类,编码为 float32 用于回归。(您可以在标题为“加载、分割和预处理数据”的单元中看到这一点。)

其他问题与记忆有关。这些模型可能会很快耗尽 GPU 内存,尤其是在作者的示例笔记本中使用的大批量数据。我通过在笔记本电脑上使用最大批量解决了这个问题。

不过,总的来说,让代码工作并不难。文档有点少,但是足够了。

分类变量处理

与 CatBoost 不同,NODE 不支持分类变量,所以您必须自己将它们准备成数字格式。我们对成人人口普查数据集采用与节点作者相同的方式进行处理,使用 category_encoders 库中的 LeaveOneOutEncoder。为了方便起见,这里我们只使用常规训练/测试分割,而不是 5 重 CV,因为训练节点需要很长时间(特别是使用超参数优化)。

现在我们有了一个全数字的数据集。

模型定义和训练循环

from category_encoders import LeaveOneOutEncoder
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_splitdf = pd.read_csv('https://docs.google.com/uc' + 
                 '?id=10eFO2rVlsQBUffn0b7UCAp28n0mkLCy7&' + 
                 'export=download')
labels = df.pop('<=50K')
X_train, X_val, y_train, y_val = train_test_split(df,
                                                  labels,
                                                  test_size=0.2)class_to_int = {c: i for i, c in enumerate(y_train.unique())}                                                                                                               
y_train_int = [class_to_int[v] for v in y_train]                                                                                                                            
y_val_int = [class_to_int[v] for v in y_val] cat_features = ['workclass', 'education', 'marital-status',
                'occupation', 'relationship', 'race', 'sex',
                'native-country']

cat_encoder = LeaveOneOutEncoder()
cat_encoder.fit(X_train[cat_features], y_train_int)
X_train[cat_features] = cat_encoder.transform(X_train[cat_features])
X_val[cat_features] = cat_encoder.transform(X_val[cat_features])# Node is going to want to have the values as float32 at some points
X_train = X_train.values.astype('float32')
X_val = X_val.values.astype('float32')
y_train = np.array(y_train_int)
y_val = np.array(y_val_int)

代码的其余部分与作者报告中的基本相同(除了 hyperopt 部分)。他们创建了一个名为 DenseBlock 的 Pytorch 层,实现了节点架构。一个名为 Trainer 的类保存有关实验的信息,并且有一个简单的训练循环来跟踪迄今为止看到的最佳指标,并绘制更新的损失曲线。

结果和结论

通过一些最小的尝试和错误,我能够找到一个验证准确率大约为 86%的模型。用 hyperopt 进行超参数优化后(本该在 Colab 的一个 GPU 上通宵运行,但实际上在大约 40 次迭代后超时),最佳性能为 87.2%。在其他跑步中,我取得了 87.4%的成绩。换句话说,经过 hyperopt 调优后,NODE 的性能确实优于 CatBoost,尽管只是略微优于 CatBoost。

然而,准确性并不是一切。为每个数据集进行昂贵的优化是不方便的。

NODE 与 CatBoost 的优势:

似乎可以得到稍微好一点的结果(基于节点纸和这个测试;我一定会尝试许多其他数据集!)

CatBoost 与 NODE 的优点:

  • 快多了

减少对超参数优化的需求

  • 没有 GPU 也能正常运行
  • 支持分类变量
  • 我会在下一个项目中使用哪一个?可能 CatBoost 仍将是我的首选工具,但我会记住 NODE,并可能尝试它以防万一…
  • 同样重要的是要认识到,性能是依赖于数据集的,成人人口普查收入数据集并不能代表所有情况。也许更重要的是,分类特征的预处理在 NODE 中可能相当重要。我将在以后的文章中回到预处理的主题!

Which one would I use for my next projects? Probably CatBoost will still be my go-to tool, but I will keep NODE in mind and maybe try it just in case…

It’s also important to realize that performance is dataset-dependent and that the Adult Census Income dataset is not representative of all scenarios. Perhaps more importantly, the preprocessing of categorical features is likely rather important in NODE. I’ll return to the subject of preprocessing in a future post!

用 Google 的 TabNet 对表格数据建模

原文:https://towardsdatascience.com/modelling-tabular-data-with-googles-tabnet-ba7315897bfb?source=collection_archive---------2-----------------------

神经网络能打败经典方法吗?

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

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

2019 年发布,谷歌研究的 TabNet 在一篇 预印稿 中声称在表格数据上胜过现有方法。它是如何工作的,怎样才能尝试呢?

如今,表格数据可能构成了大部分业务数据。想想零售交易、点击流数据、工厂中的温度和压力传感器、银行使用的 KYC(了解你的客户)信息,或者制药公司使用的模式生物的基因表达数据。品种简直是无穷无尽。

在另一篇文章的中,我介绍了 CatBoost,这是我最喜欢的在表格数据上建立预测模型的方法之一,以及它的神经网络对应物 NODE。但是大约在 NODE 手稿出来的同时,Google Research 发布了一份手稿,采用了一种完全不同的方法用神经网络进行表格数据建模。尽管 NODE 模仿决策树集成,但 Google 提出的 TabNet 试图建立一种适合表格数据的新型架构。

描述这种方法的论文名为 TabNet:专注的可解释表格学习,它很好地总结了作者们试图做的事情。“网络”部分告诉我们,它是一种神经网络,“注意力”部分意味着它正在使用一种注意力机制,它的目标是可解释的,它用于对表格数据进行机器学习。

它是如何工作的?

TabNet 使用一种软特征选择,只关注对于手头的例子重要的特征。这是通过连续的多步决策机制实现的。也就是说,输入信息是分几个步骤自顶向下处理的。正如手稿所述,“顺序形式的自上而下注意的想法是受其在处理视觉和语言数据中的应用的启发,如视觉问题回答(Hudson & Manning,2018 年)或强化学习(Mott 等人,2019 年),同时在高维输入中搜索相关信息的子集。

执行这种顺序注意的构建块被称为变压器块,即使它们与流行的 NLP 模型中使用的变压器有点不同,如 BERT 。这些变形金刚使用自我关注,并试图模拟句子中不同单词之间的依赖关系。这里使用的转换器类型试图通过使用 sparsemax 函数完成的“软”特征选择,逐步消除那些与当前示例不相关的特征。

论文中的第一张图,复制如下,描绘了信息是如何被聚合以形成预测的。

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

图片来自https://arxiv.org/abs/1908.07442

TabNet 的一个很好的特性是它不需要特征预处理(与例如节点相反)。另一个原因是,它具有“免费”内置的可解释性,为每个示例选择最相关的功能。这意味着您不必应用外部解释模块,如 shapLIME

当阅读这篇文章时,不太容易理解这个架构内部发生了什么,但幸运的是有发布的代码稍微澄清了一些事情,并表明它没有你想象的那么复杂。

怎么用?

2020 年 3 月 9 日新增:

现在 TabNet 有了比下面描述的更好的接口:一个用于 PyTorch ,它有一个类似 scikit-learn 的接口,一个用于FastAI

原始的 TabNet 代码和我的修改

正如已经提到的,代码是可用的,作者展示了如何将它与森林覆盖类型数据集一起使用。为了方便起见,他们提供了三个特定于数据集的文件:一个文件下载并准备数据(download _ prepare _ cover type . py),另一个文件定义适当的 Tensorflow 特征列和 CSV 阅读器输入函数(data _ helper _ cover type . py),以及包含训练循环的文件(experiment _ cover type . py)。

回购自述文件指出:

要将实验修改为其他表格数据集:

-替换“data/”目录下的 train.csv、val.csv 和 test.csv 文件。

-用新数据集的数字和分类特征修改 data_helper 函数,

-重新优化新数据集的 TabNet 超参数。

在用其他数据集经历了几次这个过程之后,我决定编写自己的包装器代码来简化这个过程。这个代码,我必须强调是一个完全非官方的分叉,在 GitHub 上。

根据上面的自述文件:

  1. 与其为每个数据集创建新的 train.csv、val.csv 和 test.csv 文件,我更喜欢读取整个数据集并在内存中进行拆分(当然,只要可行),所以我在代码中为 Pandas 编写了一个新的输入函数。
  2. 修改 data_helper.py 文件可能需要一些工作,至少在开始时,当您不太确定它做什么以及应该如何定义特性列时(我就是这种情况)。还有许多参数需要更改,但这些参数在主训练循环文件中,而不是在数据帮助文件中。鉴于此,我还试图在我的代码中概括和简化这个过程。
  3. 我添加了一些快速和肮脏的代码来进行超参数优化,但到目前为止只用于分类。
  4. 还值得一提的是,作者的示例代码只显示了如何进行分类,而不是回归,因此额外的代码也必须由用户编写。我已经添加了回归功能和简单的均方误差损失。

使用命令行界面

执行如下命令:

python train_tabnet.py \
  --csv-path data/adult.csv \
  --target-name "<=50K" \
  --categorical-features workclass,education,marital.status,\
occupation,relationship,race,sex,native.country\
  --feature_dim 16 \
  --output_dim 16 \
  --batch-size 4096 \
  --virtual-batch-size 128 \
  --batch-momentum 0.98 \
  --gamma 1.5 \
  --n_steps 5 \
  --decay-every 2500 \
  --lambda-sparsity 0.0001 \
  --max-steps 7700

强制参数是— -csv-path(指向 CSV 文件的位置)、--target-name(带有预测目标的列的名称)和--categorical-featues(应该被视为分类的特征的逗号分隔列表)。其余的输入参数是超参数,需要针对每个特定问题进行优化。不过,上面显示的值直接取自 TabNet 手稿,因此作者已经针对成人人口普查数据集对它们进行了优化。

默认情况下,训练过程会将信息写入您执行脚本的位置的 tflog 子文件夹中。您可以将 tensorboard 指向此文件夹,查看训练和验证统计数据:

tensorboard --logdir tflog

并将您的网络浏览器指向localhost:6006

如果你没有 GPU…

…你可以试试这款合作笔记本。请注意,如果您想查看 Tensorboard 日志,您最好的选择可能是创建一个 Google 存储桶,并让脚本在那里写入日志。这是通过使用tb-log-location参数完成的。例如,如果您的 bucket 的名称是camembert-skyscrape,您可以将--tb-log-location gs://camembert-skyscraper添加到脚本的调用中。(不过,请注意,您必须正确设置存储桶的权限。这可能有点麻烦。)

然后,您可以从自己的本地计算机将 tensorboard 指向该桶:

tensorboard --logdir gs://camembert-skyscraper

超参数优化

在 repo 中还有一个进行超参数优化的快速而肮脏的脚本( opt_tabnet.py )。同样,在合作笔记本中显示了一个例子。到目前为止,该脚本仅适用于分类,值得注意的是,一些训练参数仍然是硬编码的,尽管它们实际上不应该是硬编码的(例如,早期停止的耐心参数[在最佳验证准确性没有提高的情况下,您继续多少步]。)

在优化脚本中变化的参数是 N_steps、feature_dim、batch-momentum、gamma、lambda-sparsity。(output_dim 被设置为等于 feature_dim,正如下面的优化提示中所建议的。)

本文提供了以下关于超参数优化的提示:

大多数数据集产生 N_steps ∈ [3,10]的最佳结果。通常,更大的数据集和更复杂的任务需要更大的 N_steps。非常高的 N_steps 值可能遭受过度拟合,并且产生较差的泛化能力。

调整 Nd [feature_dim]和 Na [output_dim]的值是权衡性能和复杂度的最有效方法。对于大多数数据集,Nd = Na 是一个合理的选择。非常高的 Nd 和 Na 值可能遭受过拟合,并且产生较差的概括。

γ的最佳选择会对整体性能产生重大影响。通常,较大的 N_steps 值有利于较大的γ。

大批量有利于提高性能-如果内存限制允许,建议使用总训练数据集大小的 1–10%。虚拟批量通常比批量小得多。

最初大的学习速率是重要的,它应该逐渐衰减直到收敛。

结果

我已经通过这个命令行界面对几个数据集尝试了 TabNet,包括我在关于 NODE 的帖子中使用的成人人口普查数据集和 CatBoost ,原因可以在那个帖子中找到。方便的是,这个数据集也曾在 TabNet 手稿中使用过,作者展示了他们在那里找到的最佳参数设置。使用这些设置重复运行,我注意到最佳验证误差(和测试误差)往往在 86%左右,类似于没有超参数调整的 CatBoost】。作者在手稿中报告了 85.7%的测试集性能。当我使用 hyperopt 进行超参数优化时,不出所料,我达到了大约 86%的类似性能,尽管参数设置不同。

对于其他数据集,如扑克手数据集,TabNet 据称以相当大的优势击败了其他方法。我还没有花太多时间在这上面,但是每个人当然都被邀请在各种数据集上尝试超参数优化的 TabNet!

结论

TabNet 是一个有趣的架构,似乎有希望用于表格数据分析。它直接对原始数据进行操作,并使用顺序注意机制来为每个示例执行显式特征选择。这个属性也给了它一种内置的可解释性。

我试图通过编写一些包装代码来使 TabNet 更容易使用。下一步是在大范围的数据集上将它与其他方法进行比较。

如果你有兴趣,请在你自己的数据集上试一试,或者发送拉请求,帮助我改进界面!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值