让我们将机器学习应用于行为经济学
目前,机器学习(ML)在行为经济学或社会科学中没有得到很好的应用,这种缺乏使用来自于对这种不同方法的不熟悉。然而,随着时间的推移,随着机器学习成长起来的一代科学家将进入社会科学,并产生这些技术在计算机视觉中产生的影响。
机器学习和行为经济学之间的互动可以是互利的。一方面,ML 可以用来挖掘大量的数据,并找到促成不同行为出现的行为类型变量。另一方面,嵌入用于识别偏差和错误假设的 ML 算法将达到更高的性能。本文旨在为最先提到的最大似然法在行为经济学研究中的应用提供一种意义,即识别在塑造人们行为中起重要作用的变量。更具体地说,这篇文章简单解释了随机森林和梯度推进机等 ML 技术如何帮助行为经济学或社会心理学等相关领域的研究。
行为经济学是一个研究领域,它研究心理、认知、情感、文化和社会因素对人类决策的影响,以及这些决策如何偏离理性思维所隐含的决策。换句话说,人类不会被认为是足够理性的主体,心理变量和环境将被强调为人类决策的基本决定因素。这种方法有助于更好地预测人类行为,在某些情况下,理性思维存在方向性偏差,允许设计微调政策来改善人们的决策。
正如丹尼尔·卡内曼在他的优秀著作《思考,快与慢》中所说:“我们(人类)是模式的追寻者。“为了理解这些模式,行为经济学家在分析人们的行为时考虑了个人的心理特征和背景。然而,很难大规模地应用这种基于个人和环境的方法。机器学习可以通过检测模式并在大量数据中搜索对形成模式有影响的变量来显著解决这一挑战。
机器学习通常是关于模式识别的,它有助于自动检测数据中的模式,然后使用检测到的模式作为预测未来行动的工具。然而,对最大似然法的关注主要集中在它的预测能力上,而对它的解释能力关注较少。例如,虽然使用 ML 可以更准确和更早地诊断癌症是非常有益的,但是 ML 可以提供的更重要的东西是关于在增加癌症风险中具有更大权重的变量的洞察力。这一知识有助于研究人员和决策者更好地控制这些变量,降低癌症风险。换句话说,最大似然法不仅给了我们对目标变量的预测能力,也给了我们发现哪些输入变量在预测目标变量时更重要的知识。
为了了解 ML 在行为经济学中的应用是如何工作的,让我们来看看决策树模型,这是一个机器学习算法家族,从对一个项目的观察到关于该项目的目标值的结论。这种模型的一种形式称为分类树,其中目标变量具有一组离散的值。在这些模型中,树的分支代表输入变量的合取,这些合取导致代表目标变量的类标签的叶子。另一种决策树模型是回归树,其中目标变量具有连续值。使用决策树集成来达到高预测准确度的两个著名模型是随机森林和梯度推进机。这些基于决策树的算法的目标是建立一个模型,该模型基于几个输入变量来预测目标变量的值。这些模型的重要方面是这些模型中输入变量的数量没有限制。换句话说,他们不太担心维数灾难。
使用这些技术建立模型和进行解释的一般步骤如下:首先,数据被分成两组,一组是训练数据,其通常包括建立模型所基于的大部分数据,另一组是测试数据,通过该测试数据来验证模型。在基于训练和测试数据创建最有效的模型之后,我们将达到可以预测数据中观察到的行为的模型。这个模型现在给我们知识来识别对目标变量的预测有最大贡献的变量。数据分析中的这一过程被称为特征重要性。例如,如果我们有关于大量观察的数百个输入数据,包括个人和背景相关的变量,通过使用这种方法,我们可以发现哪些变量在引导人们表现出不同行为方面是至关重要的。
Figure 1: Decision tree path (source: http://blog.datadive.net/interpreting-random-forests/)
这些模型也提供了局部解释的可能性。换句话说,我们可以了解在每个单独的预测中最重要的顶级输入变量。例如,两个人可能出于完全不同的原因表现出相同的行为。因此,查看对某一特定行为的每个预测,并对最能解释该行为的变量进行分组,将有助于我们了解哪些特征对驱动该行为更有影响。这种可能性是由基于决策树的模型的性质决定的,在这种模型中,从分支到每片树叶都有一条特定的路径,代表每个观察的目标值(图 1)。因此,机器学习的这些强大能力不仅解决了数据规模的挑战,还使我们能够在数千或数百万人口中为一个人匹配正确的政策。换句话说,机器学习使得在正确的环境下将正确的轻推瞄准正确的人成为可能。
领英:【https://www.linkedin.com/in/atanehkar/】T2
让我们成为一个* —学习并编写一个路径规划算法来驾驶无人机
在本教程中,我们将学习并编写一个非常著名的算法,通常用于路径规划,称为 a*(A-Star)
- 简介
我们将使用 Udacity 提供的开源模拟器,让一架无人机从起点飞到终点。规划是任何自动驾驶汽车的核心能力之一。任何自动驾驶车辆在开始执行任务之前,都需要有一个计划,这个计划无非是车辆为了安全高效地从某个初始位置移动到某个目标位置而必须采取的一系列行动。从一个规划问题开始,我们需要有以下内容——A)一组所有可能的集合,车辆可以在其中找到自己(它需要在其中运行的环境的地图), b)开始状态和目标状态。c)允许车辆从一种状态转移到下一种状态的一组动作。最后,d) —从一种状态转换到另一种状态时每个可能动作的成本。 - 搜索空间、动作集和成本
那么我们能想到的代表“搜索空间”的最简单的表示法是什么呢?让我们考虑一个自上而下的二维世界观。我们可以把这个世界分成小网格。接下来,我们需要在这个表示中加入障碍。一种常见的方法是将任何包含障碍的格网单元标记为不可行。不允许车辆进入这些牢房。所有剩余的网格单元被标记为空闲单元。准备一个计划意味着找出一条路径,这条路径将通过一系列自由网格单元从起始状态到达目标状态。
Image 1
接下来我们需要的是动作场景。我们可以将移动车辆的右、左、上、下和对角运动视为我们的动作空间。对于成本函数,我们可以简单地从将向右、向左、向上和向下的动作视为成本为 1 开始。对于对角线运动,我们可以计算出,如果横向和垂直运动花费 1,那么根据毕达哥拉斯定理,对角线运动将花费 2 的平方根。每当我们制定计划时,我们可以把这个成本加起来(从起点到目标地点),并用它来比较不同的计划。
-
广度优先搜索(BFS) 现在我们有了网格世界,我们需要建立一个从开始状态到目标状态的步骤序列。那么我们如何建立这个计划呢?通过“广度”进行实际搜索的过程可以归结为几个步骤,我们将不断重复,直到我们达到目标。广度优先搜索的主干由这些基本步骤组成:
1 .将网格中的节点/顶点添加到要“访问”的节点队列中。
2。访问队列中最顶端的节点,并将其标记为这样。
3。如果该节点有任何邻居,检查它们是否被“访问过”。
4。将仍然需要“访问”的任何相邻节点添加到队列中。
5。从队列中删除我们访问过的当前节点。让我们通过从上图 1 所示地图的起始位置搜索目标位置来看看这些步骤的实际操作。我们将维护两个“队列”(称为“队列”和“已访问”),一个用于跟踪我们需要访问的节点,另一个用于列出我们已经访问过的节点。因此,从“start”位置开始,让我们将它作为队列中的第一个元素。这是 BFS 算法的第 1 步,转到第 2 步,我们将“访问”“开始”节点。访问一个节点的过程实际上意味着我们注意到它的存在,并检查它的相邻节点。在这种情况下,“开始”的相邻节点是节点 A3、B4 和 A5;因此,我们将把它们添加到我们的队列中。一旦我们访问了节点“start”并将其邻居添加到队列中,我们就可以让节点“start”出列,因为我们已经完成了需要对它做的所有事情。这时我们的 2 个队列会是这样的:
队列->A3—B4—A5;Visited - > Start 从我们的先进先出(FIFO)队列中取出下一个元素,我们将访问节点 A3,检查其相邻的空闲节点 A2 和 B3,将这些相邻节点添加到队列中,并将 A3 从队列移至“Visited”。我们更新后的队列将是这样的:
queue->B4—A5—A2—B3;再举一个例子,我们的 FIFO 队列中的下一个节点是 B4。事情变得有点有趣了。我们看到 B4 有节点 B3,“开始”,C4 和 B5 作为它的邻居。我们已经在地图上标出了障碍物和禁飞区,所以我们不考虑 C4 和 B5。接下来,我们可以在“已访问”列表中看到“开始”节点,因此我们将忽略它。最后,我们看到 B3 已经出现在队列中,所以我们也将忽略这个节点,因为它已经包含在我们的队列中。这样,我们已经检查了 B4 的所有相邻节点。我们会将它移出队列,并将其插入“已访问”列表。在访问 B4 的情况下,您可以看到 BFS 算法是如何巧妙地识别出“开始”节点先前已被访问过并且节点 B3 已经在队列中列出的事实。更新后的队列会是这样的:
queue->A5—A2—B3;参观- >开始-A3-B4
接下来我们拿 A5,重复同样的程序。我们继续这个过程,直到我们找到我们的“目标”位置。
BFS 算法的一个非常重要的特点是,它非常适合于我们想要找到任何节点到起始节点的最短距离的情况。事实上,许多 BFS 算法会跟踪每个节点的“父”节点。父节点是第一个发现有问题的节点的节点。回头看看上面我们所做的迭代,让我们考虑一下我们“访问”A3 的情况。当我们在 A3 时,我们发现 A2 和 B3 是新发现的节点。现在,当在我们的队列中添加 A2 和 B3 时,如果我们只记下是 A3 帮助我们发现了 A2 和 B3 节点,我们将有一种非常方便的方法来确定从“开始”节点到任何节点的最短距离。例如,如果我们的“目标”是 A2,一旦我们发现 A2(当我们将“访问”A3 时),我们可以注意到 A3 是它的父节点。然后,追溯“已访问”列表,我们可以发现“开始”节点将是 A3 的“父”节点。像这样,我们找到了从“开始”到 A2 的最短路径,如 A2 < - A3 < -“开始”。虽然这可能看起来是一个非常简单的例子,但我鼓励您对您感兴趣的任何节点进行这个练习,并自己发现,如果您保存有关“父”节点的信息并通过“已访问”列表追溯它,您将总是找到从“开始”到该节点的最短路径。不用说,这将保持良好,如果你改变你的’开始’节点到任何其他位置。 -
启发式
现在我们有了一张地图,我们的起点和目标位置,以及一个准备计划的方法,我们可以开始给我们的算法增加一点智能。现在,如果你在上面的广度优先搜索示例中看到,我们是在先来先服务的基础上访问节点的。例如,如果您喜欢先查看右边的节点,那么每次我们在任何节点时,它右边的节点将获得 FIFO 队列中的优先级,并且我们将总是在其他节点(左、上、下和对角线节点)之前访问这个“右边”的节点,即使其他节点离我们的目标位置更近。这似乎不是一个非常智能的系统。给定我们需要遍历的世界地图,我们可以粗略估计目标位置离所有其他节点有多远。有几种常见的方法可以做到这一点。一个例子是将从我们正在访问的节点到目标位置的欧几里德距离(或者换句话说,x 和 y 距离的平方和的平方根)作为判断访问该节点有多明智的标准。
另一种常见的方法是采用曼哈顿距离,它是到达目标的剩余 x 和 y 距离的总和。
这两种方法都忽略了障碍,并且会被低估,但它会告诉你你计划访问的节点是带你走向你的目标还是走得更远。使用这个距离,我们可以优先考虑我们想要首先访问的节点。在上面的 BFS 部分,我们看到我们随机选择相邻节点的顺序。但是现在我们可以使用这个距离信息来“访问”距离我们的“目标”最近的节点。
这些距离估计就是所谓的试探法,它们帮助我们找到规划问题的解决方案。如你所见,试探法并不完美——它们忽略了追踪不同路径的障碍和困难/成本,并且总是被低估。
所以我们得到了一个代价函数和一个启发式,接下来让我们把它们结合起来!
- 为了给搜索过程提供指导,我们现在可以使用两个函数。我们有到目前为止已经采取的行动的总和的成本函数,让我们称之为“G ”,我们有我们的启发式函数“H ”,它是达到目标状态的剩余成本(在我们的情况下,与剩余距离成比例)的表示。如果我们把这两个量加在一起,我们就得到了一个从开始一直到目标的计划总成本的估计值,尽管我们还不知道具体如何一直到目标。让我们回到我们的例子。我们将从起始状态开始,并扩展到右侧、上下的所有相邻节点,我们可以为每个节点添加一个标签,其中包含操作的总成本“G”。如果计算所有相邻节点的总成本=成本+启发式算法,我们可以看到向右或向下移动的总成本最低,这就是我们要选择的方向。根据行动的实际成本加上来自计划中最后一个节点的启发,选择总成本最低的路径的过程就是著名的算法,称为 A star。
- 坐标框架
到目前为止,我们一直在考虑如何通过连接网格中的单元找到从开始状态到目标状态的路径。但是在找到到达目标位置的路径之前,我们首先需要表示车辆的当前位置。我们将使用两个不同的坐标来表示我们的车辆的位置。第一个框架可能是我们最熟悉的一个,即使用纬度和经度来定义位置,这被称为大地框架。大地坐标系只是一个球面坐标系,其中典型的坐标‘r’、theta (θ)和 phi (φ)分别由高度、经度和纬度表示。我们将地球表面的海拔高度设置为 0,而不是中心的海拔高度为 0。
一个球形系统实际上做运动规划有点困难,因为很难用角度量来计算距离。因此我们将它们转换成更方便的本地坐标系,称为地心地球固定(ECEF)坐标系。在 ECEF,空间中的每一个点都用 x,y 和 z 来表示。然而,如果这个坐标系的原点在地球表面,会更有用。这样我们就有了自己的 ECEF 相框。
- 共线性检查
现在我们有了从 A*算法输出的计划,我们知道如何从起点到达目标位置。但是如果我们把这个计划,网格单元的序列发送给自动驾驶仪,我们真正要求车辆做的是通过一系列短目标行进。在我们一直在做的例子中,我们会告诉自动驾驶仪从我们的起始位置到目标位置。自动驾驶仪要做的是从当前位置出发,向下一格,然后停下来。接下来,我们发送下一个航路点,它将再次向下移动一个网格单元并停止。接下来,我们发送航路点,向右移动一个网格单元,然后停止等等。我们真正想让我们的车做的是向下,转向,一路行驶到最后一个网格单元,这是我们的目标位置。
接下来的问题是,我们如何将这一系列的网格单元转化为路点?我们可以使用的一种方法是获取网格单元的原始列表,只获取位于直线上的任何状态序列的开始和结束单元的网格单元。假设我们有 3 个点,如下所示。如果这三个点在同一条线上(它们共线),那么由这三个点定义的三角形的面积为零。如果我们以如下所示的矩阵形式指出这三个点的坐标,那么这个矩阵的行列式表示由这三个点形成的面积。如果这个矩阵的行列式为 0,三角形的面积为 0,这三个点共线。
在本博客的下一部分,我们将使用旧金山市的地图以及我们在这一部分学到的方法,并将它们结合起来编写一个 3D 运动规划算法。如果听起来让你兴奋,请继续关注!
直到下一次…干杯!!
让我们成为一个* —学习并编写驾驶无人机的路径规划算法—第二部分
在这一部分中,我们将建立在我们在第 1 部分中学到的理论基础上,并用 Python 编写 A*算法。
- Unity 模拟器、Python 环境安装和启动文件 在开始编码之前,我们需要安装好设置和工具。我们首先从这里下载适合您操作系统的 Unity 模拟器。接下来,我们安装所需的 Python 库。我鼓励你创建一个独立的 Python 环境(在你选择的任何 IDE 中),以保持它的整洁和独立。遵循 github repo 中的安装说明。最后,我们下载这个 github repo 来获得我们的启动文件。通过检查您是否能够运行上面提供的 github repo for starter 文件的步骤 4 中提供的说明,确保您的设置正确。
- Starter Code 从这个项目开始,我们提供了两个脚本,motion_planning.py 和 planning_utils.py。在这里,您还会发现一个名为 colliders.csv 的文件,其中包含模拟器环境的 2.5D 地图。一个 2.5D 地图只不过是一个 2D 地图(x,y 位置)和额外的障碍物“高度”信息。一旦我们开始编码,我们将更深入地研究这个文件。
motion _ planning . py 中的 main 函数与 simulator 建立“Mavlink”连接,并创建“MotionPlanning”类的“drone”对象。运动规划类包含了很多已经为我们准备好的内置函数。它包含回调函数’本地位置回调’,‘速度回调’,‘状态回调’。
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('--port', type=int, default=5760, help='Port number')
parser.add_argument('--host', type=str, default='127.0.0.1', help="host address, i.e. '127.0.0.1'")
args = parser.parse_args()conn = MavlinkConnection('tcp:{0}:{1}'.format(args.host, args.port), timeout=60)
drone = MotionPlanning(conn)
time.sleep(1)drone.start()
什么是回调函数?回调函数只有在特定事件触发时才会被执行。例如,如果您查看代码行“self.register_callback(MsgID。LOCAL_POSITION,self.local_position_callback)‘它所做的是注册 MsgID。python 字典中作为键值对的 LOCAL_POSITION 和 self.local_position_callback(在“self.register_callback”函数中实现)。现在每当 MsgID 中的值。’ LOCAL_POSITION ‘变量发生变化,就会触发对’ self.local_position_callback '函数的调用。因此它被称为回调函数。因为在正常的“顺序”程序流中不调用该函数,而是仅在特定事件发生时调用,该事件是“MsgID”值的变化。在这种特殊情况下为“LOCAL_POSITION”。
class MotionPlanning(Drone):def __init__(self, connection):
super().__init__(connection) self.target_position = np.array([0.0, 0.0, 0.0])
self.waypoints = []
self.in_mission = True
self.check_state = {} # initial state
self.flight_state = States.MANUAL # register all your callbacks here
self.register_callback(MsgID.LOCAL_POSITION, self.local_position_callback)
self.register_callback(MsgID.LOCAL_VELOCITY, self.velocity_callback)
self.register_callback(MsgID.STATE, self.state_callback)
这些回调函数构成了无人机飞行总体程序的核心结构。各种操作,如起飞,着陆,转换到航路点跟随状态,装备和解除无人机以及所需的回调功能都在这个起始代码中处理。你可以仔细阅读 motion_planning.py 文件并理解这个函数,但是由于本文的主要目的是解释“plan_path”函数的代码,所以我们不会过多地讨论起始代码。一旦无人机待命,它就成功起飞并转换到航路点跟随状态,我们需要开始一次发送一个航路点,这将由我们的“计划路径”功能在无人机起飞前生成。
一旦我们确保我们的无人机准备好了(基本上是一个系统检查,以确保所有的螺旋桨和其他系统都按照预期运行),我们在起飞前做的第一件事就是准备一条有效的路径。简单地说,我们计划在恒定高度飞行。我们也将与地图/世界中的障碍物保持“安全距离”。
目标高度= 5
安全距离= 5
- Creat_Grid 为了创建一条路径,我们需要一张地图,这是在‘colliders . CSV’文件中提供给我们的。打开文件,我们可以看到第一行包含“lat0”和“lon0”值。这些是我们地图上的家的位置或 0,0,0。
下面几行读取 colliders.csv 第一行中提供的家乡纬度和经度,并将其设置为我们的家乡位置。
# TODO: read lat0, lon0 from colliders into floating point values
lat0_lon0 = pd.read_csv('colliders.csv', nrows = 1, header = None)
lat0, lon0 = lat0_lon0.iloc[0,0], lat0_lon0.iloc[0,1]
_, lat0 = lat0.split()
_, lon0 = lon0.split()
lat0 = np.float64(lat0)
lon0 = np.float64(lon0)# TODO: set home position to (lat0, lon0, 0)
self.set_home_position(lon0, lat0, 0)
然后检查我们当前的全球位置,并将其转换为本地位置,这将是相对于我们之前设置的本地位置。然后,我们打印当前的本地和全球位置以及全球主位置值。
# TODO: retrieve current global position
# TODO: convert to current local position using global_to_local()
current_local_pos = global_to_local(self.global_position, self.global_home)
# Me: checking current local position
print ('current local position {0} and {1}'.format(current_local_pos[0], current_local_pos[1]))
print('global home {0}, position {1}, local position {2}'.format(self.global_home, self.global_position,
self.local_position))
接下来,我们用网格格式定义我们的地图,我们在这个系列的第 1 部分中学习过。我们首先从 colliders.csv 中读取完整的数据,然后将该 numpy 数组传递给名为“create_grid”的函数。
# Read in obstacle map
data = np.loadtxt('colliders.csv', delimiter=',', dtype='Float64', skiprows=3)
# Define a grid for a particular altitude and safety margin around obstacles
grid, north_offset, east_offset = create_grid(data, TARGET_ALTITUDE, SAFETY_DISTANCE)
在我们了解“create_grid”如何创建我们的网格图之前,我们需要了解 colliders.csv 中提供的数据格式。如果我们查看每列的标题(如图 2 的第 2 行所示),我们会看到给定了一个点的 3 个位置值—其 x、y 和 z 坐标,以及 3 个距离值。x、y 和 z 坐标是障碍物中心点的坐标,3 个距离是其宽度、长度和高度值。实际上它们是高度、宽度和长度的一半,因为它们是从中心点到障碍物一边的距离。下图解释了如何提供障碍物的中心点和尺寸值。
我们将创建类似于在第一部分中解释的 2.5D 结构的地图。事实上,我们将更进一步。我们已经用“目标高度”变量固定了无人机的飞行高度。因此,在创建地图时,如果障碍物的高度(PozZ + 2 * halfSizeZ)大于“TARGET_ALTITUDE”,我们将只在地图中包含该障碍物。如果障碍物的高度小于“目标高度”,我们将假设它是一个自由空间,因为我们的无人机可以飞越它。这种方法大大减少了我们的地图所需的空间和处理的复杂性。
随着高度参数的排序,我们需要确定我们需要创建的地图的大小。为此,我们首先确定 X 和 Y 方向上离原点最近和最远的点。假设 X 方向为北,Y 方向为东,我们可以得到两个方向上最近点和最远点的值,如下所示:
# minimum and maximum north coordinates
north_min = np.floor(np.min(data[:, 0] - data[:, 3]))
north_max = np.ceil(np.max(data[:, 0] + data[:, 3]))# minimum and maximum east coordinates
east_min = np.floor(np.min(data[:, 1] - data[:, 4]))
east_max = np.ceil(np.max(data[:, 1] + data[:, 4]))# given the minimum and maximum coordinates we can
# calculate the size of the grid.
north_size = int(np.ceil((north_max - north_min + 1)))
east_size = int(np.ceil((east_max - east_min + 1)))
最后,我们将网格图初始化为 numpy 个零数组
# Initialize an empty grid
grid = np.zeros((north_size, east_size))
接下来,我们将从 colliders.csv 数据中迭代每个障碍,我们已经在变量“数据”中捕获了这些数据。我们将首先比较障碍物的高度是否高于“目标高度”,如果是,那么我们将在我们的网格世界中将障碍物占据的区域+我们在变量“安全距离”中配置的安全距离标记为 1。最后,我们返回网格世界以及 north_min 和 east_min 值。
# Populate the grid with obstacles
for i in range(data.shape[0]):
north, east, alt, d_north, d_east, d_alt = data[i, :]
if alt + d_alt + safety_distance > drone_altitude:
obstacle = [
int(np.clip(north - d_north - safety_distance - north_min, 0, north_size-1)),
int(np.clip(north + d_north + safety_distance - north_min, 0, north_size-1)),
int(np.clip(east - d_east - safety_distance - east_min, 0, east_size-1)),
int(np.clip(east + d_east + safety_distance - east_min, 0, east_size-1)),
]
grid[obstacle[0]:obstacle[1]+1, obstacle[2]:obstacle[3]+1] = 1return grid, int(north_min), int(east_min)
我们需要理解的一个小警告是,碰撞器中的障碍物和我们当前的本地位置是相对于全局 home 的,这是我们在 lat0 和 lon0 变量的帮助下配置的。然而,我们在 create_grid 函数中创建的网格世界的原点在第一个障碍点。还记得我们是如何计算北部最小和最大值以及东部最小和最大值,然后计算北部和东部大小来创建网格的吗?因此,在碰撞器之间有一个偏移&我们无人机的位置值和我们网格世界中相应的相同位置。下图将帮助我们更好地理解它。
因此,每当我们试图将无人机位置传感器的值映射到网格世界时,我们需要分别减去北偏和东偏。这个偏移值就是 create_grid 函数返回的 north_min 和 east_min。我们将我们在 grid_world 中的当前位置设置为
# TODO: convert start position to current position rather than map center
grid_start = (int(current_local_pos[0] - north_offset), int(current_local_pos[1] - east_offset))
指定我们选择的目标位置
# Take GPS co-ordinates as Grid goal -Me
grid_goal = (-122.396582, 37.795714, 0)
grid_goal = global_to_local(grid_goal, self.global_home)
grid_goal = (int(grid_goal[0] - north_offset), int(grid_goal[1] - east_offset))
打印我们的起点和目标位置,然后开始我们成为 A*的旅程!!
print('Local Start and Goal: ', grid_start, grid_goal)
path, _ = a_star(grid, heuristic, grid_start, grid_goal)
- A*在城市中 我们将刚刚在上面部分创建的网格、一个启发式函数以及我们的起点和目标位置作为参数传递给 a_star 函数。希望我们在本系列的第 1 部分中获得的概念能够帮助我们更好地理解这个函数的代码。事实上,让我们重温一下我们在理解广度优先算法时所经历的步骤列表。知道算法的哪一步是在哪一行中执行的,将有助于我们更好地理解下面的代码块。如第 1 部分所述,A*算法在 BFS 的基础上增加了“成本”和“启发式”功能。因此,随着代码实现以下步骤,它将包括添加和访问’成本’和’启发式’功能了。广度优先搜索的主干由这些基本步骤组成。将网格中的节点/顶点添加到要“访问”的节点队列中。
2。访问队列中最顶端的节点,并将其标记为这样。
3。如果该节点有任何邻居,检查它们是否被“访问过”。
4。将仍然需要“访问”的任何相邻节点添加到队列中。
5。从队列中删除我们访问过的当前节点。我们首先声明几个变量。我们将使用优先级队列来存储我们需要访问的节点。优先级队列中条目的典型模式是:(优先级编号,数据)形式的元组。首先检索最低值的条目,因为我们将使用“到达相应节点的成本”值作为“优先级 _ 编号”,所以每当我们从该优先级队列中“获取”(或弹出)元素时,我们将获取具有最小“成本”值的元素。我们从“开始”节点开始。
path = []
path_cost = 0
queue = PriorityQueue()
queue.put((0, start))
visited = set(start)branch = {}
found = False# Check till we have searched all nodes or have found our ‘goal’
while not queue.empty():
item = queue.get() # Step2\. Visit the topmost node in the queue
current_cost = item[0]
current_node = item[1] if current_node == goal:
print('Found a path.')
found = True
break
else:#Step3\. If that node has any neighbors, check to see if they have been “visited” or not.
for a in valid_actions(grid, current_node):
next_node = (current_node[0] + a.delta[0], current_node[1] + a.delta[1])
new_cost = current_cost + a.cost + h(next_node, goal)#Step4\. Add any neighboring nodes that still need to be “visited” to the queue.
if next_node not in visited:
visited.add(next_node)
queue.put((new_cost, next_node)) branch[next_node] = (new_cost, current_node, a)
正如我们在第 1 部分中看到的,无论何时我们到达 BFS 算法中的一个节点,它都保证是从我们的“开始”位置到那个节点的最短路径。因此,为了跟踪我们正在跟踪的路径,在 a_star 函数中,我们将维护一个字典,其中“key”将是我们接下来要访问的节点,它的值将是当前节点的元组、为了到达下一个节点要从当前节点采取的动作以及到达下一个节点的总成本。该元组(当前节点+到达下一个节点要采取的动作)将表示从“开始”位置到达下一个节点的最短路径。
现在,一旦我们找到“目标”位置,我们将在“分支”字典的帮助下追溯到“开始”位置的路线。我们首先复制到达“目标”位置的总成本值。接下来,由于我们知道字典的“值”中存在的节点位于最短路径上,我们将构建我们的“路径”列表为->[目标,分支的值中存在的节点[目标],…]。我们将继续追踪我们的路线,直到我们找到“开始”位置,将节点添加到“路径”列表中。
if found:
# retrace steps
n = goal
path_cost = branch[n][0]
path.append(goal)
while branch[n][1] != start:
path.append(branch[n][1])
n = branch[n][1]
path.append(branch[n][1])
else:
print('**********************')
print('Failed to find a path!')
print('**********************')
return path[::-1], path_cost
- 辅助函数 *路径修剪:*一旦我们想出了路径,我们将通过删除共线点来修剪它,如第 1 部分的共线性检查一节所述。我们从最终路径中取 3 个连续点,检查它们是否在同一条线上。我们通过计算选定的 3 个点所包围的面积来实现这一点。如果它非常接近 0,我们可以假设它们位于同一直线上。我们首先将一个 3 元素(x 坐标,y 坐标,1)中的点转换为 numpy 数组。我们添加最后一个元素“1 ”,这样我们可以将 3 个顶点排列成一个漂亮的 3×3 矩阵。在对“points()”函数中的点进行整形后,我们通过连接它们来创建一个矩阵,然后使用一个方便的 np.linalg.det()函数来计算该矩阵的行列式。
有效动作:我们在 a_star 中使用的一个函数是 valid_actions (grid,current_node)。这用于执行 BFS 算法的步骤# 3,其中我们搜索当前节点的邻居。我们已经有了动作集(如第 1 部分的“搜索空间、动作集和成本”一节中所解释的)和当前节点的位置。该函数将返回动作集的子集。它将检查一个动作(即向上、向下、向左、向右或对角移动)是否会将我们带离网格或带至障碍物/禁飞区节点。如果是这种情况,它将从当前节点的有效可行动作列表中删除该动作。
回到 motion_planning.py,一旦我们得到了修剪过的路径,从其中取出每个点,将其转换为局部坐标系中的航路点(通过添加北向和东向偏移),并使用 self.send_waypoints()函数将其发送到自动驾驶仪。至此,我们完成了路径规划 A*算法的编码。所以事不宜迟,让我们启动 Udacity 的无人机模拟器,运行我们的 motion_planning.py python 文件。你可以在我的 github repo 这里找到所有的源代码。如果一切顺利,你应该能够看到你的无人机从用户配置的起点和目标位置飞行,如下图所示。
希望你喜欢阅读本系列的几篇文章,并且现在对 A*的基础有了很好的理解。
如果你觉得这篇文章有用,你知道下一步该怎么做😊直到下一次…干杯!!
让我们建立一个流数据管道
用于实时数据管道的 Apache Beam 和数据流
今天的帖子是基于我最近在工作中做的一个项目。我真的很兴奋能实现它,并把它写成一篇博文,因为它给了我一个机会去做一些数据工程,也做了一些对我的团队非常有价值的事情。不久前,我发现我们的系统中存储了大量与我们的一个数据产品相关的用户日志数据。事实证明,没有人真正使用这些数据,所以我立即对我们开始定期分析这些数据能够学到什么产生了兴趣。然而,有几个问题。第一个问题是,数据存储在许多不同的文本文件中,不能立即用于分析。第二个问题是它存储在一个锁定的系统中,所以我不能使用任何我喜欢的工具来分析数据。
我考虑过如何让我们更容易获取这些数据,并通过将这些数据构建到我们的一些用户参与工作中来真正创造一些价值。思考了一会儿之后,我决定建立一个管道,将这些数据输入云数据库,这样我和更广泛的团队就可以访问这些数据,并开始产生一些见解。在最近完成了 Coursera 上的 GCP 数据工程专业之后,我热衷于使用课程中的一些工具开始一个项目。
对,所以将数据放入云数据库似乎是处理我的第一个问题的合理方式,但是我能对第二个问题做些什么呢?幸运的是,有一种方法可以将这些数据转移到我可以访问 Python 和谷歌云平台(GCP)等工具的环境中。然而,这将是一个漫长的过程,所以我需要做一些事情,让我在等待数据传输的同时进行开发。我想到的解决方案是使用 Python 中的 Faker 库创建一些假数据。我以前从未使用过这个图书馆,但很快意识到它是多么有用。采用这种方法允许我在没有实际数据的情况下开始编写代码和测试管道。
也就是说,在这篇文章中,我将介绍我是如何使用 GCP 的一些技术来构建上述管道的。特别是,我将使用 Apache Beam (python 版本)、Dataflow、Pub/Sub 和 Big Query 来收集用户日志、转换数据并将其输入数据库进行进一步分析。对于我的用例,我只需要 beam 的批处理功能,因为我的数据不是实时到达的,所以不需要发布/订阅。然而,我将把重点放在流版本上,因为这是您在实践中可能经常遇到的。
GCP 和阿帕奇波束简介
谷歌云平台为大数据处理提供了一堆真正有用的工具。我将使用的一些工具包括:
- Pub/Sub 是一种使用发布者-订阅者模型的消息服务,允许我们实时获取数据。
- data flow是一项简化创建数据管道并自动处理诸如扩展基础设施之类的事情的服务,这意味着我们可以专注于为我们的管道编写代码。
- big query是云数据仓库。如果您熟悉其他 SQL 风格的数据库,那么 BigQuery 应该非常简单。
- 最后,我们将使用 Apache Beam ,特别是,我们将关注 Python 版本来创建我们的管道。这个工具将允许我们创建一个与 GCP 集成的流或批处理管道。它对并行处理特别有用,适合于提取、转换和加载(ETL) 类型的任务,因此如果我们需要在执行转换或计算时将数据从一个地方移动到另一个地方,Beam 是一个不错的选择。
GCP 上有各种各样的可用工具,因此很难掌握它们的全部以及它们的用途,但这里的是对它们的总结,以供参考。
可视化我们的管道
让我们使用图 1 来可视化管道的组件。在高层次上,我们想要做的是实时收集用户生成的数据,对其进行处理并将其输入 BigQuery。这些日志是在用户与产品交互时生成的,用户向服务器发送请求,然后被记录下来。这些数据对于理解用户如何使用我们的产品以及事情是否正常运行非常有用。一般来说,管道将有以下步骤:
- 我们的用户日志数据被发布到一个发布/订阅主题。
- 我们将连接到发布/订阅,并使用 Python 和 Beam 将数据转换成适当的格式(图 1 中的步骤 3 和 4)。
- 在转换数据之后,Beam 将连接到 BigQuery 并将数据追加到我们的表中(图 1 中的步骤 4 和 5)。
- 为了进行分析,我们可以使用各种工具(如 Tableau 和 Python)连接到 BigQuery。
Beam 使这个过程变得非常容易,无论我们有一个流数据源,还是我们有一个 CSV 文件并想进行批处理。稍后您将会看到,在这两者之间进行切换只需要对代码进行很小的修改。这是使用 Beam 的优点之一。
Figure 1: General Data Pipeline: Source:
使用 Faker 创建伪数据
正如我之前提到的,由于对数据的访问有限,我决定创建与实际数据格式相同的假数据。这是一个非常有用的练习,因为我可以在等待数据的同时开发代码和测试管道。如果你想知道图书馆还能提供什么,我建议你看一下 Faker 的文档。我们的用户数据通常类似于下面的例子。基于这种格式,我们可以逐行生成数据来模拟实时数据。这些日志为我们提供诸如日期、请求类型、服务器响应、IP 地址等信息。
**192.52.197.161 - - [30/Apr/2019:21:11:42] "PUT /tag/category/tag HTTP/1.1" [401] 155 "https://harris-lopez.com/categories/about/" "Mozilla/5.0 (Macintosh; PPC Mac OS X 10_11_2) AppleWebKit/5312 (KHTML, like Gecko) Chrome/34.0.855.0 Safari/5312"**
基于上面的行,我们想使用下面花括号中的 7 个变量创建我们的行变量。稍后,我们也将在表模式中使用它们作为变量名。
**LINE = """\
{remote_addr} - - [{time_local}] "{request_type} {request_path} HTTP/1.1" [{status}] {body_bytes_sent} "{http_referer}" "{http_user_agent}"\
"""**
如果我们正在做一个批处理作业,代码会非常相似,尽管我们需要在某个时间范围内创建一堆样本。要使用 faker,我们只需创建一个对象并调用我们需要的方法。特别是,faker 对于生成 IP 地址和网站非常有用。我使用了以下方法:
**fake.ipv4()
fake.uri_path()
fake.uri()
fake.user_agent()**
stream_logs.py
设置谷歌云。
注意:为了运行管道和发布用户日志数据,我使用了 google cloud shell,因为我在使用 Python 3 运行管道时遇到了问题。Google cloud shell 使用 Python 2,它在 Apache Beam 上表现得更好。
为了能够运行管道,我们需要做一些设置。对于那些以前没有使用过 GCP 的人来说,你需要完成本页中概述的 6 个步骤。
在此之后,我们将需要上传我们的脚本到谷歌云存储,并复制到我们的谷歌云外壳。上传到云存储非常简单,这里有的解释。要复制我们的文件,我们可以通过点击下面图 2 中左边的第一个图标打开工具栏中的 Google Cloud shell。
Figure 2
下面列出了我们复制文件和安装必要的库所需的命令。
**# Copy file from cloud storage
gsutil cp gs://<YOUR-BUCKET>/ * .sudo pip install apache-beam[gcp] oauth2client==3.0.0
sudo pip install -U pip
sudo pip install Faker==1.0.2# Environment variables
BUCKET=<YOUR-BUCKET>
PROJECT=<YOUR-PROJECT>**
创建我们的数据库和表
在我们完成了设置步骤之后,接下来我们需要做的是在 BigQuery 中创建一个数据集和一个表。有几种不同的方法可以做到这一点,但最简单的方法是使用谷歌云控制台,首先创建一个数据集。您可以按照下面的链接中的步骤来创建一个表和一个模式。我们的表将有 7 列 对应于每个用户日志的组件。为方便起见,除了 timelocal 变量之外,我们将所有列定义为字符串,并根据我们之前生成的变量来命名它们。我们的表模式应该如图 3 所示。
Figure 3 Table Schema
发布我们的用户日志数据
发布/订阅是我们管道的重要组成部分,因为它允许多个独立的应用程序相互交互。特别是,它充当了中间人的角色,允许我们在应用程序之间发送和接收消息。我们要做的第一件事是创建一个主题。这很简单,只需在控制台中点击“发布/订阅”并点击“创建主题”即可。
下面的代码调用我们的脚本来生成上面定义的日志数据,然后连接并发送日志到 Pub/Sub。我们需要做的唯一事情是创建一个 PublisherClient 对象,使用 topic_path 方法添加主题的路径,并在传递 topic_path 和数据的同时调用 publish 函数。注意,我们正在从我们的 stream_logs 脚本中导入 generate_log_line ,所以要确保这些文件在同一个文件夹中,否则会出现导入错误。然后,我们可以在 google 控制台中使用以下命令来运行它:
**python publish.py**
文件运行后,我们应该能够看到日志数据打印到控制台,如下图所示。这个脚本会一直运行,直到我们用 CTRL+C 杀死它。
Figure 4: publish_logs.py output
为我们的管道编码
****现在我们已经完成了初始设置,我们可以开始有趣的事情,使用 Beam 和 Python 编写我们的管道。为了创建一个波束管道,我们需要创建一个管道对象( p ) 。一旦我们创建了管道对象,我们可以使用 管道(|) 操作符一个接一个地应用多个函数。通常,工作流程如下图所示。
**[Final Output PCollection] **=** ([Initial Input PCollection] **|** [First Transform]
**|** [Second Transform]
**|** [Third Transform])**
在我们的代码中,我们创建了两个自定义函数。 regex_clean 函数,使用 re.search 函数搜索数据并根据模式列表提取适当的字符串。该函数返回逗号分隔的字符串。如果你不是正则表达式专家,我推荐你看看这个教程和笔记本来测试代码。之后,我们定义了一个名为 Split 的自定义 ParDo 函数,这是一种用于并行处理的波束变换。在 Python 中有一种特殊的方法,我们必须创建一个从 DoFn Beam 类继承的类。Split 函数从前面的函数中获取解析后的字符串,并返回一个字典列表,其中的键等于 BigQuery 表中的列名。关于这个函数,需要注意的一点是,我必须在函数中导入 datetime,它才能工作。我得到了一个错误,当我在文件的顶部导入时,这很奇怪。然后这个列表被传递给 WriteToBigQuery 函数,该函数只是将我们的数据追加到表中。下面提供了批处理数据流作业和流式数据流作业的代码。批处理代码和流代码之间的唯一区别是,在批处理作业中,我们使用 Beam 中的 ReadFromText 函数从 src_path 读取 CSV。
批处理数据流作业
main_pipeline_batch.py
流式数据流作业
main_pipeline_streaming.py
运行管道
我们可以用几种不同的方式来执行管道。如果我们愿意,我们可以从终端本地运行它,前提是我们已经远程登录到 GCP。
**python -m main_pipeline_stream.py \
--input_topic "projects/user-logs-237110/topics/userlogs" \
--streaming**
然而,我们将使用数据流来运行它。我们可以使用下面的命令做到这一点,同时还可以设置下面的强制选项。
**project**
-你的 GCP 项目的 ID。**runner**
-管道运行器,它将解析你的程序并构建你的管道。对于云执行,这必须是DataflowRunner
。**staging_location**
-云数据流的云存储路径,用于存放执行工作的工人所需的代码包。**temp_location**
-云数据流的云存储路径,用于存放在管道执行期间创建的临时作业文件。**streaming**
**python main_pipeline_stream.py \
--runner DataFlow \
--project $PROJECT \
--temp_location $BUCKET/tmp \
--staging_location $BUCKET/staging
--streaming**
当这个命令运行时,我们可以在 google 控制台的 DataFlow 选项卡上查看我们的管道。当我们点击管道时,我们应该看到如图 4 所示的内容。出于调试的目的,进入日志,然后使用 Stackdriver 查看详细的日志会很有帮助。这帮助我在很多情况下解决了管道的问题。
Figure 4: Beam Pipeline
在 BigQuery 中访问我们的数据
没错,我们应该建立管道,让数据流入表中。为了证实这一点,我们可以转到 BigQuery 并查看数据。使用下面的命令后,您应该会看到数据集的前几行。现在我们已经将数据存储在 BigQuery 中,我们可以做进一步的分析,与同事共享数据,并开始回答和解决业务问题。
**SELECT * FROM `user-logs-237110.userlogs.logdata` LIMIT 10;**
Figure 5: BigQuery
外卖食品
希望这提供了一个创建流数据管道的有用示例,也提供了一个找到使数据更易访问的方法的有用示例。拥有这种格式的数据对我们有很多好处。我们现在可以开始回答一些有用的问题,比如有多少人使用我们的产品?用户群是否会随着时间的推移而增长?人们与产品的哪些方面互动最多?还有有没有不该发生的错误发生?这些是组织感兴趣的问题类型,基于这些见解,我们可以推动产品的改进并提高用户参与度。
Beam 对于这种类型的练习非常有用,并且还有许多其他有趣的用例。例如,您可能希望实时分析股票报价单位数据,并根据分析进行交易;您可能有来自车辆的传感器数据,并希望计算出流量水平。例如,你也可以是一家游戏公司,收集用户数据,并利用这些数据创建跟踪关键指标的仪表板。好了,伙计们,这就是另一个帖子,感谢阅读,对于那些想看完整代码的人,下面是我的 GitHub 的链接。
推荐课程:[【GCP】上的数据工程、大数据、机器学习](http://Data Engineering, Big Data, and Machine Learning on GCP)
在 Google Cloud Platform 中为用户日志数据创建流管道-d poly/User _ log _ Pipeline
github.com](https://github.com/DFoly/User_log_pipeline)
注意,这篇文章中的一些链接是附属链接。****
使用 LDA 构建文章推荐器
Photo by Ian Schneider on Unsplash
由于对学习新主题的浓厚兴趣,我决定从事一个项目,其中一个潜在狄利克雷分配(LDA) 模型可以根据搜索短语推荐维基百科文章。
本文解释了我用 Python 构建项目的方法。请查看下面 GitHub 上的项目。
使用 LDA,该项目基于搜索查询推荐维基百科文章。-kb22/文章推荐者
github.com](https://github.com/kb22/Article-Recommender)
结构
Photo by Ricardo Cruz on Unsplash
我使用类在 Python 中开发了完整的项目,并没有像我通常做的那样使用 Jupyter 笔记本来理解类以及如何开发一般的 Python 项目。模块、维基百科浏览器、清理器和内容被定义为Modules
文件夹中的类。 config 文件包含了配置。收集数据、生成器和评估器用于开发和运行模型。
**Modules
|- __init__.py
|- WikipediaCrawler.py
|- Cleaner.py
|- Content.py****config.yml
collectData.py
generateDLA.py
evaluator.py****sample_images
|- recommendations.png****.gitignore
LICENSE
Pipfile.lock
README.md
requirements.txt**
当您尝试运行项目时,您可以使用Pipfile.lock
或requirements.txt
来安装所有的依赖项。
配置
Photo by Tim Mossholder on Unsplash
将项目的任何配置包含在一个公共文件中总是一个好的做法。虽然这个项目中没有太多的信息,但是我定义了在config.yml
文件中存储数据库、LDA 模型、字典和语料库的路径。我决定把这些都放在data
文件夹里。
配置文件基于 YAML,这是业内常用的数据序列化方法,用于存储人类可读的配置。需要pyyaml
包来读取 Python 中的 YAML 文件。
模块
Photo by Louis Reed on Unsplash
我开发和设计了三个模块(作为类),用于从维基百科抓取数据,并处理这些数据。
维基百科爬虫
类WikipediaCrawler
让我们根据某个类别抓取维基百科的文章。在初始化这个类时,它创建一个sqlite3
连接,然后添加一个表wikiData
,存储页面id
、category
、url
和content
。collect_data
方法使用wptools
包提取页面并将它们存储在表中。wptools
是一个 Python 包,允许我们根据给定的类别抓取维基百科的文章。
我添加了两个额外的方法,get_ids
获取所有的页面 id,get_urls
获取所有的 URL,如果需要的话。
清洁工
该模块接收文档文本并对其进行预处理。我只需要使用函数clean_text
,因为它代表我们调用所有其他函数并返回最终结果。它执行以下操作:
- 删除不必要的新行字符
\n
- 删除标点符号
- 删除数字
- 删除停用字词(过于常见且不适合作为搜索关键字的字词)
- 应用词条化(将每个单词转换为其词条单词,如 ran,running 转换为 run
内容
这个模块连接到sqlite3
数据库,帮助我们迭代页面,并使用Cleaner
模块清理它们的内容。我添加了其他方法来通过 id 获取页面和 url 。
应用
Photo by Jason Leung on Unsplash
一旦我设置好模块,我就开始搜集数据,训练 LDA 模型并推荐文章。
收集数据
首先,我运行文件collectData.py
,它期望两个参数开始从 Wikipedia 中提取数据并将其存储在数据库中。
- **类别:**我们要为其开发文章推荐系统的类别
- **深度:**对于给定的类别,我们想要提取网页到什么深度。例如,当从深度 2 开始浏览一篇文章时,它将以深度 1 更深入一步(即其相关文章),但将在下一深度结束,因为它将是 0。
如果目录data
不存在,它将创建该目录。使用WikipediaCrawler
,它提取页面并存储到wikiData.db
供其他文件使用。完成后,它输出消息:数据库已经生成
生成 LDA
下一步是使用我们创建的数据库,从它构建一个 ld a 模型,并将其存储在 data 文件夹中。
首先,我读取数据库并创建一个字典。我删除所有出现在少于 5 个文档中的单词和出现在超过 80%文档中的单词。我尝试了多个值,并通过反复试验得出了这些数字。然后,使用doc2bow
,我创建一个单词包,作为关键字列表。最后,我生成了 LDA 模型,并保存了模型、词典和语料库。
求值程序
最后,一切准备就绪。我们调用evaluator.py
并传入一个查询字符串,基于该字符串我们识别关键字并列出匹配搜索标准的前 10 篇文章。
我阅读查询并从中识别关键字。然后,通过调用get_similarity
方法,我计算了相似度矩阵,并按照降序对它们进行排序,这样最大相似度的文档就在顶部。
接下来,我迭代这些结果,并呈现代表推荐文章的前 10 个 URL。
真实的例子
用例
我用深度 2 和类别机器学习创建了数据库。它生成了文件,wikiData.db
。接下来,使用generateLDA.py
,我创建了 LDA 模型。
使用
我使用搜索查询作为Machine learning applications
,并被推荐了如下图所示的文章:
Recommended articles for ‘Machine learning applications’
结论
在本文中,我讲述了如何开发一个 LDA 模型,根据搜索查询向用户推荐文章。我使用 Python 类设计了一个完整的应用程序。
如果你喜欢这篇文章,看看我的其他文章:
[## 使用 Flask、Flask RESTPlus 和 Swagger UI 处理 API
Flask 和 Flask-RESTPlus 简介
towardsdatascience.com](/working-with-apis-using-flask-flask-restplus-and-swagger-ui-7cf447deda7f) [## 使用机器学习预测心脏病的存在
机器学习在医疗保健中的应用
towardsdatascience.com](/predicting-presence-of-heart-diseases-using-machine-learning-36f00f3edb2c) [## 使用 ROC 和 CAP 曲线的机器学习分类器评估
了解 ROC 和 CAP 曲线及其在 Python 中的实现
towardsdatascience.com](/machine-learning-classifier-evaluation-using-roc-and-cap-curves-7db60fe6b716)
一如既往,请随时分享您的想法和想法。如果你在一个项目或一个想法上需要帮助,在 LinkedIn 上 ping 我。你可以在这里找到我。
让我们计算一下纽约 Airbnb 价格的 Z 值
通过编码示例了解 z 分数
z 分数,也叫标准分数,根据维基百科的说法是。
**翻译:**衡量一个值与其总体平均值的差距。
我们来看看公式。
μ = mean, σ = std, x = value
这很简单。从被评估的值中减去数据集的平均值,然后除以标准差。
我们来玩一些数据。
首先,从 Kaggle 下载数据集,并将其保存在与您的 jupyter 笔记本相同的目录中(您的文件名可能与我的不同)。然后对前几条记录进行采样,看看数据看起来如何。
import pandas as pd
ny = pd.read_csv('airbnb-new-york/AB_NYC_2019.csv')
ny.head(3)
Scipy 有一个很好的方法来计算数据集中每个值的 z 值。见下文。
from scipy import stats
stats.zscore(ny.price)#=> array([-0.01549307, 0.30097355, -0.01132904, ..., -0.15707024,
-0.4069123 , -0.2611711 ])
但是我们将编写自己的函数,因为我们只想检查几个值。
import numpy as npmean = np.mean(ny.price)
std = np.std(ny.price)def z_score(value, mean, std):
return (value - mean) / std
现在从数据集中随机选择 5 个价格。
import randomvalues = []# randomly select 5 values from price
for i in list(range(0,5)):
value = random.choice(ny.price)
values.append(value)print(values)
#=> [78, 169, 53, 375, 80]
并计算这些值的 z 分数。
for val in values:
z = z_score(val, mean, std)
print(z)#=> -0.3111395124662796
#=> 0.06778761869937604
#=> -0.41524037267662456
#=> 0.9255787068326184
#=> -0.302811443649452
嘣。这到底意味着什么?
第一个示例比平均值低 0.31 个标准偏差。
第二个是平均值以上 0.07 个标准差。
3 号比平均值低 0.42 个标准差。你明白了。
所有的例子都在平均值的 1 个标准偏差内。这在任何服从正态分布的人群中都不是闻所未闻的(其中约 68%的值落在平均值的 1 个标准差之内)。
但是什么是正态分布呢?它有时被称为钟形曲线(尽管其他分布也遵循钟形)。
It’s the red line here. Thank you wikipedia.
是上面的红线。在自然科学和社会科学中,人口中的许多值都遵循这种分布。例如,大多数人的身高接近所有人的平均身高。而且很少有人超级高或者超级矮。这在直觉上是有道理的。
但并非一切都遵循正态分布。我怀疑我们的 Airbnb 价格。让我们建立一个直方图来找出答案。
import matplotlib.pyplot as plt
%matplotlib inlineplt.rcParams.update({'figure.figsize':(7,5), 'figure.dpi':100})plt.hist(ny.price, bins=100, range=(0, 1000))
差远了。
出于好奇,我们的均值和标准差是多少。
print(mean)
print(std)#=> 152.7206871868289
#=> 240.15171391941718
因此,虽然平均值是 153 美元,但标准差却高达 240 美元。这就是说价格变化很大。这就是为什么$78 的值和$375 的值都可以在平均值的 1 个标准偏差之内。
将我们的分布与标准偏差为 1 的正态分布进行对比!
为什么所有这些都很重要?在一个沉迷于快速统计数据和平均值的世界里。重要的是要记住,人口的平均值只是故事的一小部分。
让我们回到基础
数据偏好和数据成功之间的反比关系
Photo by Martin Adams on Unsplash
几年前,说每家公司都是科技公司很快就成了陈词滥调。当然,在某种程度上,每个公司都是如此。但是另一个趋势紧随其后,而且这个趋势更加适用:每个公司都在成为数据公司。见鬼,每个组织都在成为数据组织!成为数据驱动的好处是显而易见的;显然,如果你能根据数据做出决策,并衡量这些决策的影响,这是一种比过去推动业务发展的猜测或直觉更有效的方法。因此,市场上出现了数据驱动的强劲趋势也就不足为奇了,部分原因是云技术和 SaaS 的使用越来越多,使得数据比以往任何时候都更容易访问。
但没那么快。我还看到,尽管人们对数据驱动的兴趣比以往任何时候都大,但擅长数据驱动却比以往任何时候都难。事实上,我要说的是,围绕数据的基本要素(准确性、质量、可靠性、治理、访问、保护、安全性)实际上有一个相反的趋势—这些都非常难以正确处理,并且越来越难以管理。
但问题在于:这些趋势不仅同时存在,它们实际上还相互强化。也就是说,你越想成为数据驱动型(趋势上升),就越难把基本面搞对(趋势下降)。数据越不可靠,组织就越不重视他们的数据工作……这种恶性循环还在继续。
对此我们能做些什么?首先,我认为思考一下我们是如何走到这一步的是有帮助的。数据偏好和数据成功之间的这种反比关系有许多原因。首先,数据被视为蛋糕上的表面糖衣,而不是蛋糕本身,重点是人工智能、仪表盘和数据的 UI 方面,而不是从一开始就重新想象结构。此外,或许与此相关的是,数据在整个组织中往往是一个分散的目标。
这需要改变这两种现实,以确保我们不会因为使用不可靠的数据而分散自己的努力。从许多方面来说,这相当于通过思维方式的转变回归基础:
- 没有数据总比有不可靠的数据好
- 要信任数据,我们必须知道它来自哪里,它意味着什么,它基于什么样的假设
- 我们必须从数据源到仪表板、报告或我们使用的机器学习模型,全程管理数据的可靠性
为什么我们在业务中所做的每一件事的可靠性标准都如此之高,这是最初鼓励这种数据驱动趋势的一部分!—但不知何故,我们已经接受了我们的数据不可靠的事实?我每天都被这个困扰着。如果你有同样的感觉——你看到了什么帮助组织扭转这一悖论的趋势?
让我们通过预测垃圾邮件来了解 ROC AUC 曲线
ROC AUC 曲线比较分类器不同分类阈值的 TPR 和 FPR。
ROC AUC 曲线通过评估模型区分不同类别的能力,帮助我们选择工作的最佳模型。
图例:
ROC =受试者操作曲线
AUC =曲线下面积
TPR =真阳性率
FPR =假阳性率
在深入了解这一切意味着什么之前,让我们为一个真实的例子绘制一条曲线。
举例:预测哪些短信是垃圾短信
首先,从 Kaggle 下载数据集。
在熊猫数据框中打开它。
import pandas as pddf = pd.read_csv('~/Downloads/spam.csv', encoding='ISO-8859-1')
df.head(3)
它看起来像是格式化不良的数据。数据通常就是这样——我们会解决它。
将v1
转换成你的标签y
,将v2
转换成你的特征X
。标签需要是整数才能输入到模型中,所以if spam
设置为1
,而if ham
设置为0
。不知道的话,ham
表示不是spam
。
import numpy as npy = np.array([(1 if i=='spam' else 0) for i in df.v1.tolist()])
X = np.array(df.v2.tolist())
X
现在是字符串数组,y
是1's
和0's
的数组。
将数据分成测试集和训练集。请注意,数据尚未矢量化。
from sklearn.model_selection import StratifiedShuffleSplitsplitter = StratifiedShuffleSplit(
n_splits=1, test_size=0.3, random_state=0
)for train_index, test_index in splitter.split(X, y):
X_train_pre_vectorize, X_test_pre_vectorize = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
安装矢量器并转换测试和训练集。
from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer()X_train = vectorizer.fit_transform(X_train_pre_vectorize)
X_test = vectorizer.transform(X_test_pre_vectorize)
选择一个分类器并将其安装在训练集上。我任意选择了LogisticRegression
。
from sklearn.linear_model import LogisticRegressionclassifier = LogisticRegression()
classifier.fit(X_train, y_train)
通常这是我们预测测试集的类的地方,但是因为我们只是对构建 ROC AUC 曲线感兴趣,跳过它。
让我们预测类的概率,并将结果转换成一个数组。
y_score = classifier.predict_proba(X_test)
y_score = np.array(y_score)
print(y_score)
The 1st index of each inner array is the probability the example’s class is 0. The 2nd index is the probability that example’s class is 1.
下面的代码可能有点混乱。对 3 个(或更多)类使用label_binarize()
会将单个y
值[2]
转换为[0 0 1]
,或者将[0]
转换为[1 0 0]
,但是对仅有的 2 个类就不一样了。所以我们调用 numpy 的hstack
来重新格式化输出。
from sklearn.preprocessing import label_binarizey_test_bin = label_binarize(y_test, neg_label=0, pos_label=1, classes=[0,1])y_test_bin = np.hstack((1 - y_test_bin, y_test_bin))
print(y_test_bin)
Our label binarized output.
生成曲线。
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as pltfpr = dict()
tpr = dict()
roc_auc = dict()for i in [0,1]:
# collect labels and scores for the current index
labels = y_test_bin[:, i]
scores = y_score[:, i]
# calculates FPR and TPR for a number of thresholds
fpr[i], tpr[i], thresholds = roc_curve(labels, scores)
# given points on a curve, this calculates the area under it
roc_auc[i] = auc(fpr[i], tpr[i])
此时,我们可以分别计算 0 类和 1 类的 ROC 曲线。但是为了简单起见,我们将把它们结合起来,生成一条曲线。
**免责声明:**这在类平衡时更有意义,否则它可能会掩盖一个事实,即模型在一个类中表现很差,而在另一个类中表现很好。但是我们还是会在这里学习如何做。
我们将使用“微平均”并对两个类别的 TPR 进行拉平,对 FPR 也是如此。会为我们做这件事。例如,[[1,0],[0,1]]
变成了[1,0,0,1]
fpr["micro"], tpr["micro"], _ = roc_curve(y_test_bin.ravel(), y_score.ravel())
roc_auc['micro'] = auc(fpr["micro"], tpr["micro"])
现在画出曲线。
plt.figure()
lw = 2
plt.plot(fpr[1], tpr[1], color='darkorange',
lw=lw, label='ROC curve (area = %0.2f)' % roc_auc[1])
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()
My code for plotting was inspired by sklearn docs.
不错!这是一个非常棒的曲线。
橙色曲线下的面积越大,模型就能更好地区分类别。从另一个角度来看,曲线越靠近左上角越好。
我们所说的“不同的分类阈值”是什么意思?
如果你习惯使用现成的 sklearn 分类器,你会知道.predict
输出预测的类。但是您可能不知道这是基于默认的 50%分类阈值。
大多数分类器,像LogisticRegression
都有一个叫做predict_proba()
的方法来预测一个例子落入每个类别的概率,而不是调用.predict()
。
使用这个你可以使用你指定的任何阈值重新计算输出的类。
高曲线好背后的直觉是什么?
一个正确分类大多数例子,并且输出接近0.0
(不是这个类)或者1.0
(是这个类)的预测概率的模型,会有一条类似上面的曲线。
它告诉我们,改变分类阈值不会对模型的分类输出产生太大影响,因为模型非常确信每个例子要么是一个1
要么是一个0
。
例如,如果模型输出 SMS 消息是垃圾消息的预测概率为0.9
,则将阈值从0.5
改变为0.6
对输出的类别没有影响。该模型对其输出的类非常有信心。
相反,如果模型在0.3
到0.7
范围内输出大多数预测概率,那么将阈值从0.5
移动到0.6
将改变输出的类别。你可以说像这样的模型对自己预测的分类不自信。
为什么是 ROC 和 AUC?
该曲线是“ROC 曲线”,其描绘了不同阈值下的 TPR 和 FPR。
AUC 只是曲线下面积的计算。这是一种不用看曲线就可以量化模型准确性的方法,或者是在目测曲线下的区域不能给出明确的赢家时,比较两个模型之间的曲线。
什么是 TPR?
真实阳性率。
TPR 是 TP 数除以 TP 和 FN 之和。
示例 1: 预测图像是否是狗的模型。
TP :正确预测了一个狗的形象就是狗。
FN: 错误预测一个狗的形象是猫。
例 2 :预测消息是否为垃圾邮件的模型。
TP :正确预测出一条垃圾短信是垃圾短信。
FN :错误预测垃圾短信是 HAM。
什么是 FPR?
假阳性率。
FPR 是 FP 的数除以 FP 和 TN 的和。
例 1: 一个预测图片是否是狗的模型。
FP :错误预测一只猫的形象是一只狗。
TN: 正确预测一个形象不是狗。
例 2: 预测消息是否为垃圾邮件的模型。
FP :错误地预测到一条 HAM 消息是垃圾消息。
TN :正确预测一条火腿消息是火腿。
ROC AUC 曲线做得不好的地方
ROC 曲线不是不平衡数据集的最佳选择。当两个阶层的人数持平时,他们是最好的。否则,模型在对特定类别进行分类时表现良好,可能会掩盖模型在预测其他类别时表现不佳的事实。
外卖
- ROC AUC 曲线可以让我们深入了解模型在区分类别方面的预测能力。
- AUC 较高的型号通常比 AUC 较低的型号性能更好。
- ROC AUC 不是严重不平衡数据集的最佳选择。
让我们做个交易
解决蒙蒂霍尔问题的三种方法
你的心脏跳得很厉害。你既兴奋又紧张,不知道接下来会发生什么。你以前从未上过电视,但你很了解这项运动。由老谋深算、魅力非凡的蒙蒂·霍尔领衔的《让我们做笔交易》是目前最受欢迎的电视节目之一。自 1965 年以来,你一直是一名普通观众,无数次看到蒙蒂与参赛者进行交易。有一次,你看到一个女人用 50 美元换取一个装有 1000 美元的信封。还有一次,你看到一个人拒绝了 800 美元,而是保留了一个盒子,结果发现里面只有纸巾!现在轮到你了。
蒙蒂·霍尔转向你。很快,就像广播员的剧本一样,“在这三个标有 1、2 和 3 的门后面,有一辆新的克尔维特;另外两个藏着不值钱的山羊。没错,绝对不值钱的山羊。现在那里,你会有哪扇门?”
你听到一个无声的“咩咩”声,但你不确定它来自哪里。
“我要 1 号门,”你自信地说。
“绝佳的选择!如你所见,3 号门后面是一只毫无价值的山羊。”3 号门打开了,一只黑白斑点的公山羊出现了。它几乎是可爱的,尽管它实际上一文不值。在你的城市公寓里养一只山羊的想法很滑稽,而且你也不知道如何卖掉一只山羊。
蒙蒂继续说道,“1 号门是你的最终选择吗?你可以转到 2 号门,但不能回头。我不希望在这里做出错误的决定。”
你考虑一下情况。你最初的选择有⅓概率是正确的,但是你被授予了新的信息,3 号门现在退出了。汽车一定在 1 号门或 2 号门后面,但是是哪一个呢?你考虑如果你从 1 号门换到 2 号门,却发现汽车一直在 1 号门后面,你会感到多么沮丧。也许坚持最初的选择是最好的…或者是吗?
蒙蒂·霍尔问题
蒙蒂霍尔问题是一个经典的脑筋急转弯,测试一个人用条件概率进行推理的能力。另外,问题的前提增加了一个心理边缘;许多人选择留在原来的车门,只是为了避免从汽车切换的失望。在这篇文章中,我将演示 3 种方法来确定最佳策略,留下或转换。
频率主义方法
相对来说,多次模拟 Monty Hall 游戏并记录游戏的胜败次数是比较容易的。从统计学的角度来说,我们可以将游戏中的每一次尝试视为伯努利试验(具有二元结果的随机实验),成功概率为 P。根据大数定律,如果实验次数 N 非常大,则预期胜率为 100 * P%。通过记录停留和转换策略的许多试验的结果,可以使用 z 检验来确定胜率是否存在统计上的显著差异。
下面的 python 代码定义了一个模拟游戏的函数monty_hall
。关键字参数switch
控制是采用停留还是切换策略。此外,设置关键字参数verbose = True
允许一个人以互动的方式玩游戏,并当场选择是留下还是转换。
import numpy as npdef monty_hall(chosen_door, switch = False, verbose = False): '''
Parameters
----------
chosen_door : Initial choice of door, must be either 1, 2, or 3.
switch : False --> stay strategy
True --> switch strategy.
verbose: False --> no input required
True --> player is asked if they'd like to switch
doors. overrides switch input.
Returns
------
1 : if the game is won
0 : if the game is lost
'''
# Correct indexing for chosen door
chosen_door += -1
# Randomly initialize array to represent the 3 doors
# Array contains two 0s for goats and a single 1 for the car
doors = [0, 0, 0]
doors[np.random.randint(0,3)] = 1
# Reveal a door concealing a Goat
revealed_door = np.random.choice([i for i, x in enumerate(doors) if x == 0 and i != chosen_door])
switch_door = list({0, 1, 2} - {chosen_door, revealed_door})[0]
# If verbose == True ask the player if they'd like to switch.
input_string = f"You've chosen door {chosen_door + 1}, an excellent choice! As you can see behind door {revealed_door + 1} is a worthless goat. Is door {chosen_door + 1} your final choice? You may switch to door {switch_door + 1} but there's no going back. Enter y if you'd like to switch."
if verbose == True:
choice = input(input_string)
if choice == 'y':
switch == True
else:
switch == False
# Return the result of the game
if switch == False:
result = doors[chosen_door]
else:
result = doors[switch_door]
if verbose == True:
if result == 0:
print("How unfortunate, you've chosen a worthless goat!")
if result == 1:
print("Congratulations, you've just won a new Corvette!")
return result
让我们用这个函数对停留和转换策略进行 100,000 次游戏。结果将保存在停留和切换列表中,并将打印每个策略的胜率。
i = 0
stay = []
switch = []while i < 100000:
stay.append(monty_hall(np.random.randint(1, 4)))
switch.append(monty_hall(np.random.randint(1, 4), switch = True))
i += 1
print(f"Stay win rate: {round(100 * np.sum(stay) / 100000, 1)}%")
print(f"Switch win rate: {round(100 * np.sum(switch) / 100000, 1)}%")
入住率:33.3%
切换成功率:66.6%
显然,这两种策略之间有很大的区别。尽管在这里并不真的需要统计检验来检测差异,但我们还是要对数据进行双尾 z 检验。
from statsmodels.stats.weightstats import ztesttest_statistic, p_value = ztest(stay, switch)
print(f"Test Statistic: {round(test_statistic, 1)}")
print(f"P-Value: {round(p_value, 5)}")
测试统计:-157.5
p 值:0.0
z-test 返回的测试统计值为-157.5,p 值为 0(实际上是一个无穷小的数字,我们的计算机已将其四舍五入为 0)。因此,可以完全肯定地拒绝零假设,即逗留和转换策略的成功率是相同的。事实证明,转换显然是更好的策略!
但是为什么切换要好得多呢?为了更好地理解,让我们从贝叶斯的角度来看这个问题。
贝叶斯方法
与频率统计相比,贝叶斯统计为因果关系提供了另一种视角。频率主义统计学试图回答这个问题,“给定我的假设,我的数据有多大的可能性?”另一方面,贝叶斯统计试图回答这个问题,“给定我的数据,我的假设有多大的可能性?”这种差异是微妙而强大的。
Thomas Bayes, English minister, philosopher, and statistician known for deriving the rule that bears his name. However, the modern version used today was derived by the French mathematician Pierre-Simon Laplace.
贝叶斯统计的基石是贝叶斯规则,这是一个简单的恒等式,告知如何在给定新数据的情况下正确更新对假设的信念。
P(H|D) = P(D|H)*P(H) / P(D)
H =假设,D =新观察到的数据
P(H|D)是给定新的观察数据时我们假设的概率。P(D|H)是假设假设是正确的,观察到数据的概率。P(H)是我们在观察新数据之前对假设正确的概率的评估,也称为“贝叶斯先验”。P(D)是在任何情况下观察到数据的概率,无论假设是否成立。
对于 Monty Hall 问题,有两种可能的假设:H1)汽车在最初选择的门后面,以及 H2)汽车不在最初选择的门后面,并且切换将导致获胜。现在让我们考虑一下 H1,看看如何用贝耶法则来确定这个假设的概率。
首先,考虑假设 P(H1)的先验概率。请记住,最初的选择是从三个选项中做出的,这三个选项都同样可能是正确的。P(H1),做出最初正确选择的概率,简单来说就是 1/3。
接下来,考虑观察到新数据的概率,假设 H1 是正确的。如果汽车实际上在最初选择的门后面,那么山羊出现在其他门的概率是 1/2,因为蒙蒂会在他的两个选项中随机选择。H1 = 1/2。
Billy goat. Isn’t he cute?
最后,考虑在任何情况下观察到新数据的概率。因为可能只有两个互斥的假设,P(D)可以用下面的等式来确定,P(D) = P(D|H1)P(H1) + P(D|H2)P(H2)。我们已经知道 P(D|H1) = 1/2,P(H1) = 1/3。P(H2) = 2/3,是最初做出错误选择的概率。最后,P(D|H2) = 1/2,因为如果我们的第一扇门隐藏了一只山羊,剩下的两扇门都有相等的概率隐藏第二只山羊。总而言之,P(D) = 1/2 * 1/3 + 1/2 * 2/3 = 1/2。
我们总共有:P(H1) = 1/3,P(H2) = 2/3,P(H1)= 1/2,P(H2)= 1/2,P(D) = 1/2
现在可以将这些值插入到贝叶斯规则中,以确定汽车在我们最初选择的门后面的概率。
P(H1 | D)= P(D | H1)* P(H1)/P(D)=(1/2 * 1/3)/(1/2)= 1/3
看来新的信息并没有增加最初选择正确的可能性!
当我们这样做的时候,让我们也使用贝叶斯法则来确定 H2 的概率,即汽车不在我们最初选择的后面,以及转换将导致胜利。
P(H2 | D)= P(D | H2)* P(H2)/P(D)=(1/2 * 2/3)/(1/2)= 2/3
That billy goat sure is cute, but most people would rather have this awesome 70s Corvette.
我们现在使用贝叶斯推理来证实我们的频率主义者实验的结果。此外,为 H1 和 H2 确定的贝叶斯概率与观察到的胜率完全一致!到目前为止,为什么换工作比呆在公司好得多已经变得越来越清楚了,但是让我们以最后一种方式来看待这个问题,以便真正理解这一点。
博弈论方法
蒙蒂霍尔问题当然是欺骗性的,但它并不特别复杂。通过思考所有可能的结果,人们可以很快得出结论:转换是最优策略。
考虑转换策略会赢或输的情况。当初始门隐藏汽车时,切换将会失败。当初始门隐藏一只山羊时,蒙蒂将被迫露出第二只山羊,剩下的门将隐藏汽车,切换将总是获胜。由于最初的选择将仅在 3 场游戏中的 1 场游戏中隐藏汽车,所以转换策略将在 3 场游戏中输掉大约 1 场,并赢得另外 2 场。相反,留下来只会赢得最初选择恰好正确的三分之一的比赛。
蒙蒂拍摄一个不耐烦的样子,“现在任何一天伙计。我开始觉得我应该把那辆新的克尔维特给山羊!”
“咩咩”黑白相间的公山羊似乎同意蒙蒂的观点。
“我最后问一次,你愿意换到 2 号门吗?”
在突然的顿悟中,你想起了你读过的一篇关于这个主题的文章。虽然你不能确定,但你明白你的选择的可能性。抵制住坚持己见的诱惑,接受自己造成的不幸可能带来的尴尬,你平静地宣布“我要换到 2 号门。”
为了更深入地探究天魔堂问题,它的历史、心理学和许多变化,请查看杰森·罗森豪斯的《天魔堂问题。
我们用机器学习做一些分子吧!⚛️
米(meter 的缩写))医学和材料科学
一个新分子到达大众市场平均需要 10 年。
【1912 年 4 月, RMS 泰坦尼克号 在它的处女航中与大西洋上的一座冰山相撞,淹没了 1500 条生命,创造了历史。几十年后,沉船被发现,仍然充满了 20 世纪的历史。在打捞泰坦尼克号几个月后, 挑战者号航天飞机 将在全国数百万人热切的目光注视下发射。发射 73 秒后,航天飞机在半空中爆炸。
Historically relevant material failures
这两起灾难的根本问题不在于人为错误和设计。**问题出在素材上。**泰坦尼克号船体的失败程度不亚于挑战者号的右火箭助推器。我们记得这些悲剧事件,不是因为它们发生了,而是因为它们本来是可以避免的。泰坦尼克号和挑战者号建造时已知的更好的材料。但是仅仅知道存在更好的材料是不够的;它们必须是可用的和适用的。
问题是,一个分子从了解到应用需要大量的时间。今天,平均时间是 10 年。
According to Scientifist, the average time for a drug to complete this process is 12 years and 1.5B dollars (courtesy of Scientifist)
10 年足够花费数万亿美元,5 亿人死亡,数百万小时浪费在解决问题上,如果我们有合适的材料或药物,这些问题本来可以不那么严重。
这并不新鲜。从历史上看,科学进步是缓慢的。几个世纪以来,由石头制成的工具一直是标准的,定期开处方的药物治疗通常弊大于利。直到人类重视科学的进步,我们才开始创造更好的技术,从而开始了正反馈循环:
有一段时间,这种反馈循环导致了稳定的线性增长。但是随着我们对科学掌握的增长,我们的技术也在进步。随着人工智能等技术的出现,这种增长必然会呈指数级加速。
人工智能给了研究人员更多对分子空间的控制,从而在科学领域掀起了波澜。
在我的上一篇文章中, A.I 增强了分子发现和优化 ,用 A.I 在高水平上讨论了当前加速科学的方法。在这里,我将介绍我最近的一个项目,该项目遵循当前在科学领域的 ML 应用研究中使用的最常见的工作流/管道。
项目小说
这个项目的目标是用递归神经网络产生新的分子。这些分子可能没有用,甚至可能无效,但想法是训练模型学习微笑串中的模式,使得输出类似于有效分子。SMILES 是一个分子的字符串表示,基于给定分子的结构和组件。例如,环丙沙星看起来有点像这样:
Colors correspond to the character (Courtesy of Wikipedia)
神经网络的选择取决于我们要输入的数据类型。在这种情况下,我们将向它提供微笑字符串作为数据,因此递归神经网络(RNN)最适合这项工作。习惯于用相对更高效和有效的方式来增强 RNN 的内部结构;LSTM 牢房。我们将使用由 LSTMs 组成的几个层,在超过 200,000 个微笑字符串的数据集上进行训练。
步骤 1 —映射:
与大多数语言处理 rnn 一样,第一步是创建字符到整数的映射**(允许神经网络处理数据),反之亦然(将结果翻译回字符)。**
最简单的方法是创建一组独特的字符,并枚举每一项。在像英语这样的自然语言中,有 26 个字母(是大写字母的两倍),以及大量的语法符号和语法字符。在微笑字符串中,有两种类型的字符:
- 特殊字符:“/”、“(”、“”、“=”等。
- 元素符号:“C”、“O”、“Si”、“Co”等。
这个独特字符的列表被列举并方便地放入字典中。
值得注意的是字典不认为由两个字符组成的元素符号是一个元素。例如,硅元素符号的“S”和“I”算作两个独立的字符。这意味着模型必须了解“Si”和“S”之间的区别,这两者是完全不同的元素。将这两个字符的符号硬编码到字典中是完全可能的,但只是为了好玩,我将它们分开,以观察模型的表现如何。
第 2 步—数据预处理:
一旦有了惟一的字符映射,就可以着手将 SMILES 字符串数据集中的每个字符转换成整数。简单调用我们在第一步中构建的字典就可以了。
同时,您可以通过简单地将每个整数除以数据集中唯一字符的总数来规范化所有的整数。最后,将得到的整合和标准化数据集重新整形为适合神经网络模型的格式。
步骤 3 —模型架构
使用 Keras,构建模型非常简单。
Play around with the numbers! Try more layers, more neurons, more dropout, whatever you fancy!
我们的模型有多层,每一层都有数量递减的神经元;你可以随意摆弄这些数字。这是一个很大的概括,但经验法则是,神经网络中的层和神经元越多,计算量越大,但其性能也越好。
步骤 4—检查点:
我们都经历过。你已经在你一直在写的文章或报告上取得了很大进展,但突然你的电脑出了故障——你所有的工作都消失在深渊中。顺便提一下,训练神经网络也有同样的问题。谢天谢地,有一个解决方案。
还记得在新的马里奥游戏中,一旦你过了一半,游戏会保存你的进度吗?
For nostalgia, and to emphasize how important it is to SAVE!!! (courtesy of TrustedReviews)
检查点是 Keras 库中的一个内置功能,它允许我们**将我们的训练进度(模型在任何给定时期的权重)保存在一个文件中,然后可以将其传输到另一个设备或保存起来供以后使用。**检查点可能是最不受欢迎的,也是最有用的机器学习技术之一。
Don’t forget to call the callbacks parameter when fitting your model!
检查点对于将训练从预测、分类或生成步骤中分离出来特别有用。在将节省下来的重量加载到 CPU 上之前,通过在 GPU 或云服务上进行训练,你可以减少完成一个项目所需的时间。因此,检查点对于迁移学习或者简单地暂停和恢复训练是有用的。它们还可以用于对每个改进时期的模型输出进行采样,为网络模型增加一点透明度。
第 5 步—培训:
我使用分类交叉熵作为带有Adam
优化器的损失函数(混合了RMS-prop
和ADAgrad
以及内置动量)。为了利用尽可能多的数据集,该模型有 19 个 512 批次大小的时期可供学习。一般来说,更多的历元与更小的批量配对允许网络更好地从数据中学习,但代价是更长的训练时间。
第 6 步—生成:
发电相对简单。首先,我们导入从训练中保存的检查点(这样我们就不必在每次想要生成新分子时重新训练模型)。下一步是从数据集中随机选择一个 SMILES 字符串作为参考,最后生成指定数量的字符。
The exact code is used for generating any kind of text - only in this case, the text is a molecule.
根据数据集大小的选择、学习率和其他可以对模型进行的自定义调整,结果会有所不同,但理想情况下,您应该得到类似于有效分子的输出。如果你插入一个精确度较低的检查点文件,你实际上可以**看到神经网络是如何学习并随着时间变得更好的。**通常,它以一系列只有一次的字符开始:
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
但是随着时间的推移,包含交替字符会变得更好
C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1 C1
最终,随着它学习子结构,输出将更加多样化
C1 C1 C1 C1 C1 C1 C1((((((((((/ccccccchchchchchchchchc)))))))))
目标是尽可能接近
O1C = C[C @ H]([C @ H]1 O2)C3 C2 cc(OC)C4 c3oc(= O)C5 = C4 CCC(= O)5
尽可能。
可能的改进
这个项目可以用两种不同的方式来增强,一些以数据为中心,另一些以模型本身的架构为中心。
数据集
20 万个分子的数据集虽然令人印象深刻,但如果再大一些也不会有什么坏处。除了在互联网上搜寻更多的微笑字符串,一个可能实现的技术是数据扩充。
Data augmentation as explained using Andy Warhol paintings
数据增强实际上类似于安迪·沃霍尔的画;取一张图片,稍加修改,然后添加到数据集。从本质上说,这是数据扩充,尽管过于简单。对微笑字符串也可以这样做;获取字符串,找到它的排列,并将其添加到您的数据集中。
在这种情况下,项目的目标是生成尽可能接近有效的分子,而不是具有特定或期望属性的分子。这消除了偏向一种特定类型分子的结果的风险,因此增加我们现有的 200k 分子数据集是安全的。我们可以**通过枚举超出其规范形式的微笑字符串来扩充数据,**这基本上意味着以另一种方式编写相同的微笑字符串,所有这些字符串最终都表示相同的分子。这给了模型更多的字符串来学习,因为每个分子都有许多不同的微笑字符串,其数量随着分子大小和复杂性的增加而增加。
建筑
在这个项目中,LSTM 被用作各层的主节点。然而,还有许多其他强有力的选择。神经图灵机(NTM) 和它的兄弟例如,可微分神经计算机(DNCs) 都是由谷歌的研究人员创造的最先进的架构。两种架构都很强大。NTMs 和 DNCs 令人印象深刻,因为它们有一个外部组件,可以比 LSTMs 更好地“记住”事情。这个外部组件充当一种存储体。
The DNC architecture (courtesy of The Nature magazine)
你可以在这些博客文章(NTMs, DNCs )和关于它们的研究论文(NTMs,DNCs)中读到关于它的所有内容,或者在我的文章中获得关于 RNN 建筑家族所有成员的高层次概述。本质上,这两个强大的替代方案都有内置注意力机制的记忆库,这允许它们选择性地记忆数据的部分(在这种情况下是文本,但也可以是图像),这被认为是非常重要的。这在生成具有所需特性的分子方面可能非常有用,因为一些特性如毒性是由分子内的亚结构决定的,当翻译成微笑时,它是该串的特定部分。
还有其他因素需要考虑,比如其他可能的数据集和格式的丰富性。不同类型的分子表示代替微笑,例如 SMARTS)和架构改进(例如对抗性训练或强化学习的使用)。这两种改进已经有了一些值得注意的实现。
专用工具
有一些非常棒的工具可以用于大规模和高质量的制作,但是没有多少人知道。计算材料科学、生物学、化学和物理学项目将使用如下软件包:
- Matminer —一个用于材料科学数据挖掘的
Python
库 - Magpie —基于
Java
的材料性能预测 ML 库 - PyMKS——一个专门研究结构-属性关系的
Python
图书馆 - deep chem(—
Python
一个让科学领域的 ML 民主化的图书馆 - Openbabel —一个用于生物和化学信息学的
Python
和C++
库
这些专门的包与 Python 的内置和扩展库一致,这意味着它们可以与 numpy、scipy、matplotlib 等一起使用。对于项目。未来的工作将探索这些工具包能做什么!
下一步是什么+关键要点
Project Novel 是用基本的 M.L 工具和固体数据集生成分子的最基本方法的一个例子。大多数研究将结合其他架构的方面,如对抗训练,或强化学习,以提高生成分子的有效性,或使模型结果偏向具有所需属性的分子。
即使在最好的情况下,LSTMs 对生成的文本样本的说服力也是有限的。原因在于微笑字符串本身,这是**不是计算表示分子的最佳方式。**在不久的将来,可能会有更好的框架专门用于化学信息学和相关科学领域。然而,目前标准仍然停留在递归神经网络和分子串表示的动态组合上。
在我的下一篇文章中,我们将深入探究科学和人工智能的交汇点在哪里,以及随着人工智能加速科学的发展,会发生什么变化。
关键要点
- 材料和药物的失败是研发速度缓慢的结果
- 缓慢的研发速度可以通过像 M.L 这样的指数技术得到改善
- 人工智能应用于科学最流行的方式是使用循环神经网络和分子串表示法
- 递归神经网络和分子串表示法不是将 M.L .应用于科学的最佳方式
- 人工智能和科学的交叉仅仅触及了表面;项目仍处于初级阶段,有很多东西值得期待!
需要看更多这样的内容吗?
跟我上LinkedIn, 脸书 ,insta gram,当然还有 中
我总是希望结识新朋友、合作或学习新东西,所以请随时联系flawnsontong1@gmail.com
向上和向前,永远和唯一🚀
📝稍后在杂志上阅读这个故事。
🗞每周日早上醒来,你的收件箱里会有本周最值得关注的科技故事、观点和新闻:获取值得关注的时事通讯>
让我们开始吧——PUBG 一步一步教程
大家好!这款笔记本是专门为想要进入 Kaggle 竞赛领域但不知道如何开始旅程的初学者准备的。
最有效的方法,就是去做!
嗯,我相信这个。对于初学者来说,这是一个很好的平台,可以让他们在正在进行的比赛中动手。我鼓励你们所有人在这个 PUBG 比赛中尝试并获得乐趣。
Photo by Kony Xyzx on Unsplash
从 Kaggle 页面
player unknown ’ s BattleGrounds(PUBG)广受欢迎。销量超过 5000 万份,是有史以来第五大畅销游戏,每月有数百万活跃玩家。
你得到了大量匿名的 PUBG 比赛统计数据,格式化后每行包含一名球员的赛后统计数据。数据来自各种类型的比赛:单人赛、双人赛、团队赛和定制赛;不能保证每场比赛有 100 名玩家,也不能保证每组最多 4 名玩家。
你必须创建一个模型,根据玩家的最终统计数据预测他们的最终排名,范围从 1(第一名)到 0(最后一名)。
基本上,我们被要求在不玩游戏的情况下预测每个玩家的最终位置。多酷啊!这就像一个成功的公式,
哦,你有这么多大头照,你会进入前 5 名的。
太神奇了,不是吗?
在这个游戏中,有 n 个不同的人用不同的策略玩,知道什么能帮助你赢得游戏会很有趣。
和大多数 Kaggle 比赛一样,你会得到两个数据集:
- 一个训练集,包含一组球员的结果(或目标变量)以及其他参数的集合,如他们的 groupId、matchId、助攻等。这是您必须在其上训练预测模型的数据集。
- 一个测试集,你必须根据为两个数据集提供的其他玩家属性来预测现在未知的目标变量。
为了让事情运转起来,我们将从简单的方法开始,这些方法在逻辑上对所有人来说都是合理的。在我们前进的道路上逐渐转向深度学习方法。是的,你没听错。深度学习在 r .让我们不要忘乎所以。
让我们首先从加载这些库开始。
读取训练和测试文件
数据一瞥
只是我,还是你们也注意到了测试文件中有一个列不匹配(有一列丢失了)?
winPlacePerc 列是我们在测试中要预测的。如果在测试文件中有,这就不是一个挑战了,对吗?
让我们更深入地研究我们的目标变量。
好吧,这样更容易理解。0.4583 是训练集中的中值。这当然意味着大多数玩家都在完成游戏的区间**(努力)**的中间。你准备好做你的第一个预测了吗?既然大多数玩家都完成了中等级别的训练,也许假设测试组中的每个人都完成了是一个好的开始?一点玩快速脏,并发送一个预测。
您可能已经注意到,我们在目标值中有一个空值。除此之外,它是一个超级干净的数据集。(警告:这不是真实情况)。所以我们用 0 来估算。
现在在机器学习中,主要有两类变量。
- 分类变量,其值是有限的,通常基于特定的有限组。例如,分类变量可以是国家、年份、性别、职业。
- 然而,连续变量可以取任何值,从整数到小数。例如,我们可以得到收益,股票价格。
机器学习算法不支持字符。所以我们要把它们转换成因子(分类)。
我们的第一次预测出了什么问题?让我们分析一下。
我们看到了中间值(或最常出现的值)并将其应用于预测文件,希望其他所有玩家都在 try hards 类别中。我们是多么自信啊,啊!
欢迎来到超适合
- 一个好的机器学习模型的目标是从训练数据很好地推广到来自问题域的任何数据。这使我们能够根据模型从未见过的数据对未来做出预测。
- 过拟合指对训练数据建模过好的模型。
- 过度拟合发生在模型学习训练数据中的细节和噪声,以至于对模型在新数据上的性能产生负面影响的时候。这意味着训练数据中的噪声或随机波动被模型拾取并学习为概念。问题是这些概念不适用于新数据,并对模型的概括能力产生负面影响。
在我们的例子中,通过查看中间值,我们认为它将适用于测试数据中的所有其他值。这是数据的过度拟合。
如何解决过度拟合?
- 一个验证数据集仅仅是你的训练数据的一个子集,你把它从你的机器学习算法中保留下来,直到你的项目结束。在训练数据集上选择并调整了机器学习算法后,您可以在验证数据集上评估学习到的模型,以最终客观地了解模型在未知数据上的表现。
EDA 和特征工程
嗯,理论上讲步行距离应该是获胜的关键因素。由于跑步速度几乎是静止的,你对此无能为力。*所以,如果你必须处于领先的位置,你就必须继续前进。*步行距离与获胜几率成正比。
提升也应该是一个重要因素。如果你想多活些时间,很有可能你使用了一次或多次强化。
我们还看到了表示游戏模式(如小队、双人和单人模式)的预期分组数。小于 10 的值可能需要进一步调查,因为这些很可能是自定义游戏或断开错误。
相互关系
让我们看看变量之间的相关性。这可能会给我们一个良好的开端,以防从长远来看所有的变量都被认真考虑。
正相关——步行距离、武器获得、提升
负相关 —必杀之地
杀戮地点 —在比赛中杀死的敌方玩家数量排名。等级越低,赢得比赛的机会就越大。
1.NumGroups —我们在比赛中拥有数据的组的数量。
有趣的是,当 numGroups 为 1 时,只有的值为 0。让我们记住这一点,并在测试数据中进行更改。(一些后期处理)。
2.步行距离——步行的总距离,以米为单位。
似乎数据有很多离群值,这使得(或骗子)在训练数据。时速超过 20 公里是不切实际的。我们会认为这是一个欺骗代码,并继续前进。
3 —杀死
在这里,我为那些拥有超过 40 次杀戮的 id 创建了一个标志
4.头像率
爆头杀 可能有很多可以告诉你一个玩家有多好。我已经创建了一个变量 headshot_rate 来理解头部枪击致死率与总致死率的关系。
这里,我们创建了 test1 作为验证集,以便在真实测试数据集上进行测试之前进行进一步测试。
僵尸
问题陈述
主要有两种类型的机器学习问题
- 分类 —输出变量采用类别标签。
- 回归 —输出变量取连续值
这里,由于目标变量(winPlacePerc)是一个连续变量,该问题属于回归问题。
线性回归是回归的首选方法。
线性回归用于根据一个或多个输入预测变量 x 来预测结果变量 Y 的值。目的是在预测变量和响应变量之间建立线性关系(数学公式),这样,当只有预测变量(Xs)值已知时,我们可以使用该公式来估计响应变量 Y 的值。使用我们的 PUBG 数据,假设我们希望对资产、kills 和 winPlacePerc 之间的线性关系进行建模。
Y = β1 + β2X + ϵ
其中,β1 是截距,β2 是斜率。统称为回归系数。ϵ是误差项,y 的部分回归模型无法解释。
是线性的!
- 在后台,代表“线性模型”的 lm 通过最小化最小二乘准则产生最佳拟合线性关系。
- 对于我们模型的初始评估,我们可以使用 summary。这为我们提供了关于我们的模型的大量信息,我们将逐一介绍。
斯捷潘克
在逐步回归中,选择程序由统计包自动执行。变量选择的标准包括调整的 R-square、Akaike 信息标准(AIC)、贝叶斯信息标准(BIC)、Mallows 的 Cp、PRESS 或错误发现率(1,2)。逐步选择的主要方法是向前选择、向后淘汰和两者的结合
平均绝对误差
平均绝对误差(MAE) 是另一个用于回归模型的损失函数。MAE 是我们的目标变量和预测变量之间的绝对差值的总和。因此,它测量的是一组预测中误差的平均大小,而不考虑它们的方向。(如果我们也考虑方向,这将被称为平均偏差误差(MBE),它是残差/误差的总和)。
H2O——图书馆
H2O 是一个开源、内存、分布式、快速、可扩展的机器学习和预测分析平台,允许您在大数据上构建机器学习模型,并在企业环境中轻松实现这些模型。
此外,它还使用内存压缩来处理大型数据集,即使是小型集群也是如此。它还包括实现并行分布式网络培训规定。
深度学习
希望你们学到了一些东西。建设性的反馈总是受欢迎的。
这里有完整的代码:https://github.com/Arjundasmarath/PUBG
跟着我上kaggle:https://www.kaggle.com/arjundas
让我们玩 21 点(用 Python)
我们用 Python 实现了一个 21 点模拟器,以便更好地理解去拉斯维加斯的风险
这篇文章绝不是试图推广 21 点或赌博行为。任何时候你在赌场赌博,赔率都对你不利——随着时间的推移,你会输钱。不要拿你输不起的东西去冒险!
更正: 我意识到(感谢@DonBeham 的友好提示)在某些情况下,我的 total_up 函数错误地处理了多个 ace。我已经在我的 GitHub 上更新了下面的代码和 。我道歉!
我最近没有为博客写任何代码,所以我想写一篇与编程相关的文章。概率和统计的经典应用之一是对机会游戏(赌博)的研究。碰运气的游戏(纸牌游戏、骰子游戏等。)是统计学家的最爱,因为它们既展示了随机性,也展示了某种必然性:
- 随机在那场你不知道会有什么结果的游戏中。
- 而必然在于你知道大量游戏的平均结果会是什么。
今天,我们将通过用 Python 编写一个 21 点模拟器来研究 21 点,模拟一系列游戏,然后研究我们的玩家做得如何。我将假定您对 21 点游戏有一些基本的了解,但这里有一个关于该游戏玩法的快速复习:
- 玩家下注。
- 玩家发 2 张牌。
- 庄家得到 2 张牌,第二张牌对玩家是隐藏的。
- 游戏的目标是获得比庄家更高的总点数(但不超过 21,任何超过 21 的点数都是自动损失,称为破产 ) —如果你以这种方式击败庄家,你将从赌场赢得你所赌的钱(如果庄家破产,你也将获胜)。ace 可以值 1 或 11;其他每张牌的面值都相同(正面牌的面值为 10)。
- 由一张 a 和一张脸牌组成的最初 2 手牌被称为 21 点,是最好的一手牌。
- 在第一轮发牌后,每个玩家都可以选择打(收到更多的牌)或留(没有更多的牌)。如果击中导致玩家失败(总数超过 21),那么他或她的赌注就输了。
- 在所有玩家都完成击球/停留后,庄家翻开他隐藏的牌。如果庄家的总数少于 17,那么他或她需要击中(收到一张新卡)。这个过程一直重复,直到庄家的手牌总数达到 17 或更多,或者破产(超过 21)。
- 庄家玩完之后,就决定了最后的结果——如果庄家破产,那么任何没有先破产的玩家就赢了他或她的赌注。如果庄家没有破产,那么庄家的总数将与每个玩家的总数进行比较。任何玩家的总点数大于庄家的,他或她就赢了钱(按下注的金额)。任何玩家的总点数少于庄家的,他或她就输钱。在平局的情况下不兑换货币。
如果你想了解更多关于 21 点规则的内容,请访问这个网站。开始编码的时间到了!
编写我们的模拟器
使用面向对象编程可能是个好主意。但是在这一点上,我还不习惯用那种方式写代码。我可能会在将来的某个时候修改我的代码,使之面向对象;但这是另一天的项目。
首先,让我们把输入语句放在一边:
import numpy as np
import pandas as pd
import random
import matplotlib.pyplot as plt
import seaborn as sns
现在让我们创建几个函数来帮助我们。首先,我们需要一个函数来创建一副新的纸牌供我们玩。 make_decks 函数就是这么做的——它为每种牌类型添加四张牌(ace、2、3、4 等等)。)到列表 new_deck ,洗牌 new_deck ,并返回新创建的列表(deck)供我们玩。还要注意,我们可以通过 num_decks 来指定我们希望该函数创建多少副牌。
# Make a deck
def make_decks(num_decks, card_types):
new_deck = []
for i in range(num_decks):
for j in range(4):
new_deck.extend(card_types)
random.shuffle(new_deck)
return new_deck
我们还需要一个函数,可以增加我们手中的卡的价值。它比简单的求和稍微复杂一些,因为 ace 可以值 1 或 11,这取决于哪一个对持有者最有利。因此,我们的函数首先计算手中每张非 a 牌的值(我用数字 10 表示所有的正面牌,因为它们在 21 点中的功能都是相同的)。然后它会计算 ace 的数量。最后,它决定了每张 a 值多少钱,这取决于你其余牌的价值。
**修正:**在我之前版本的代码下面,有一个错误。为了解决这个问题,我添加了助手函数 ace_values ,它将您手中的 ace 数作为整数输入,并输出您的 ace 值的唯一值列表。计算给定数量的 ace 的排列(和它们的和)比我想象的要多,我需要写下面两个助手函数来完成它(更多细节,请参考我在下面代码块中的注释):
# This function lists out all permutations of ace values in the
# array sum_array.
# For example, if you have 2 aces, there are 4 permutations:
# [[1,1], [1,11], [11,1], [11,11]]
# These permutations lead to 3 unique sums: [2, 12, 22]
# Of these 3, only 2 are <=21 so they are returned: [2, 12]
def get_ace_values(temp_list):
sum_array = np.zeros((2**len(temp_list), len(temp_list)))
# This loop gets the permutations
for i in range(len(temp_list)):
n = len(temp_list) - i
half_len = int(2**n * 0.5)
for rep in range(int(sum_array.shape[0]/half_len/2)):
sum_array[rep*2**n : rep*2**n+half_len, i]=1
sum_array[rep*2**n+half_len : rep*2**n+half_len*2, i]=11
# Only return values that are valid (<=21)
return list(set([int(s) for s in np.sum(sum_array, axis=1)\
if s<=21]))# Convert num_aces, an int to a list of lists
# For example if num_aces=2, the output should be [[1,11],[1,11]]
# I require this format for the get_ace_values function
def ace_values(num_aces):
temp_list = []
for i in range(num_aces):
temp_list.append([1,11])
return get_ace_values(temp_list)
以上两个函数现在可以由函数 total_up 使用,该函数像我上面提到的那样,计算我们手中牌的价值(包括正确处理任何 a):
# Total up value of hand
def total_up(hand):
aces = 0
total = 0
for card in hand:
if card != 'A':
total += card
else:
aces += 1
# Call function ace_values to produce list of possible values
# for aces in hand
ace_value_list = ace_values(aces)
final_totals = [i+total for i in ace_value_list if i+total<=21]
if final_totals == []:
return min(ace_value_list) + total
else:
return max(final_totals)
既然我们的助手函数已经完成了,让我们进入主循环。首先,我定义了我的关键变量:
- 牌堆是我们将模拟的牌堆数量(其中每个牌堆可以是一副或多副)。
- 玩家是每个模拟游戏的玩家数量。
- num_decks 是每叠牌中的牌数。
- card_types 是所有 13 种卡类型的列表。
stacks = 50000
players = 1
num_decks = 1card_types = ['A',2,3,4,5,6,7,8,9,10,10,10,10]
现在开始我们模拟器的主循环。有两个:
- 一个 for 循环,遍历我们想要模拟的 50,000 叠牌。
- 一个 while 循环,对于每叠牌,玩 21 点,直到该叠牌中有 20 张或更少的牌。此时,它移动到下一个堆栈。
numpy 数组, *curr_player_results,*是一个重要的变量,其中存储了每个玩家的游戏结果——1 表示赢,0 表示平,1 表示输。这个数组中的每个元素对应于 21 点桌上的一个玩家。
在 while 循环中,我们给每个玩家发一张牌,然后给庄家发一张牌(Python 注释说“先发一张牌”),然后我们再发一次,这样每个人都有 2 张牌。为了发牌,我利用了输入为 0 的 pop 函数——它返回列表的第一个元素,同时将它从列表中删除(非常适合从一堆牌中发牌)。当庄家牌堆中剩余的牌数下降到 20 张或更少时,新的牌堆用于替换旧的牌堆(移动到 for 循环的下一次迭代)。
for stack in range(stacks):
blackjack = set(['A',10])
dealer_cards = make_decks(num_decks, card_types)
while len(dealer_cards) > 20:
curr_player_results = np.zeros((1,players))
dealer_hand = []
player_hands = [[] for player in range(players)] # Deal FIRST card
for player, hand in enumerate(player_hands):
player_hands[player].append(dealer_cards.pop(0))
dealer_hand.append(dealer_cards.pop(0))
# Deal SECOND card
for player, hand in enumerate(player_hands):
player_hands[player].append(dealer_cards.pop(0))
dealer_hand.append(dealer_cards.pop(0))
接下来,庄家检查他或她是否有 21 点(a 和 10)。请注意,在前面的代码块中,我将 21 点定义为包含 a 和 10 的集合。
如果庄家有 21 点,那么玩家就输了(并在 curr_player_results 中得到-1),除非他们也有 21 点(在这种情况下是平局)。
# Dealer checks for 21
if set(dealer_hand) == blackjack:
for player in range(players):
if set(player_hands[player]) != blackjack:
curr_player_results[0,player] = -1
else:
curr_player_results[0,player] = 0
如果庄家没有 21 点,游戏继续。玩家自己做 21 点检查——如果他们有,他们就赢(在一些赌场,21 点支付 1.5 比 1,换句话说,如果你的赌注是 100 美元,那么你就赢 150 美元)。我通过将数组 curr_player_results 中对应于该玩家的元素设置为 1 来记录一次胜利。
对于没有 21 点的玩家,他们现在可以选择击球、停留等。对于这个模拟,我的目标是捕捉所有类型的玩家决策——聪明的、幸运的和愚蠢的。所以我基于抛硬币来为玩家做决定(如果 random.random()产生的值高于玩家命中的 0.5,否则玩家留下)。
通过抛硬币来决定可能听起来很傻,但通过使击中/停留决定与游戏中实际发生的事情无关,我们可以观察所有类型的情况,并最终生成丰富的数据集进行分析。
在这个例子中,我并不试图找出最佳策略。相反,我想使用这个模拟器来生成训练数据,用这些数据我最终可以训练一个神经网络来最佳地玩 21 点(在未来的帖子中)。
对于 Python 来说,\字符表示行继续符,可以用来格式化超长的代码行,以获得更好的可读性——在下面的代码块中,你会看到我使用它。
else:
for player in range(players):
# Players check for 21
if set(player_hands[player]) == blackjack:
curr_player_results[0,player] = 1
else:
# Hit randomly, check for busts
while (random.random() >= 0.5) and \
(total_up(player_hands[player]) <= 11):
player_hands[player].append]
(dealer_cards.pop(0))
if total_up(player_hands[player]) > 21:
curr_player_results[0,player] = -1
break
在我们循环的最后一段(快到了!),轮到庄家了。庄家必须打到他或她破产,或者有一手至少等于 17 的牌。因此,while 循环向庄家发牌,直到达到 17,然后我们检查我们的庄家是否破产。如果庄家破产,那么每个还没有输(通过破产)的玩家都会赢,我们会在 curr_player_results 中为他们记录 1。
# Dealer hits based on the rules
while total_up(dealer_hand) < 17:
dealer_hand.append(dealer_cards.pop(0))
# Compare dealer hand to players hand
# but first check if dealer busted
if total_up(dealer_hand) > 21:
for player in range(players):
if curr_player_results[0,player] != -1:
curr_player_results[0,player] = 1
如果庄家没有破产,那么每个玩家都将自己的手牌与庄家的手牌进行比较,较高的手牌获胜。
else:
for player in range(players):
if total_up(player_hands[player]) > \
total_up(dealer_hand):
if total_up(player_hands[player]) <= 21:
curr_player_results[0,player] = 1
elif total_up(player_hands[player]) == \
total_up(dealer_hand):
curr_player_results[0,player] = 0
else:
curr_player_results[0,player] = -1
最后,在每场 21 点游戏结束时,我们将游戏结果以及我们关心的其他变量添加到列表中,我们将使用这些列表来跟踪我们的整体模拟结果:
# Track features
dealer_card_feature.append(dealer_hand[0])
player_card_feature.append(player_hands)
player_results.append(list(curr_player_results[0]))
模拟结果
很好,我们现在可以检查一些结果了。我运行了 50,000 副牌的模拟器。由于使用了这么多副牌,最终出现了:
- 玩了312,459 二十一点游戏。
- 玩家在游戏的199403**(当时的 64% )输了**。
- 玩家在游戏 99,324 (当时 32% )中赢得。
- 玩家在游戏的13732**(时间的 4% )中与**打成平手。
我们可以看看赢/平概率(不把钱输给赌场的概率)是如何由于关键的可观察因素而变化的。例如,下面是所有可能的庄家牌的赢/平概率(回想一下,玩家在决定怎么做时只能看到其中一张庄家的牌):
Probability of Win or Tie vs. Dealer’s Shown Card
从 2 到 6,赢/平的概率增加。但是在 6 之后,这种可能性急剧下降。那很有趣。让我们思考一下为什么会出现这种情况:
- 如果庄家亮出一张低牌,那么在其他条件相同的情况下,他的总牌价更低,这对玩家来说更容易被击败。这部分解释了为什么从 2 到 6 的概率平均高于从 7 到 ace 的概率。
- 此外,记住庄家的规则——如果他的总数少于 17,他必须击中。如果他必须出手,他很有可能会破产。这解释了为什么概率从 2 增加到 6。这么想吧— **最常见的卡值是多少?它是 10,因为每副 52 张牌中有 16 张(4 张 10 张牌,j、q 和 k)。**因此,如果庄家显示的是 6,(假设我们没有算牌),我们庄家最有可能的初步总数是 16。因为 16 小于 17,所以他必须击中。还有很多牌可能会让他破产——任何值 6 或以上的牌。同样的逻辑也适用于庄家出 5 的情况,只是少了一张会让他破产的牌(现在必须是 7 或更多)。
- 现在让我们想一想,当庄家出 7 时会发生什么。在这种情况下,另一张隐藏的牌值 10。那么拥有 16 分或更少的玩家将会觉得必须去击球。如果他们不这样做,失败的概率是实质性的。但是如果他们真的击中了,那么他们很有可能会失败(由于所有的 10 分)。
简而言之,这就是赌场对 21 点玩家的优势——通过在玩家行动时隐藏庄家的一张牌(并迫使玩家在庄家之前行动),赌场迫使 21 点玩家做最坏的打算,并让自己面临被捕的风险(这很重要)。
因此,如果你在赌场,你拿到的牌总数在 12 到 16 之间,祝你好运,因为你已经成为赌场的标志,现在你的胜算很大。让我们来看看玩家的初始手牌值(他或她的初始两张牌的值)如何影响他或她的赢/平概率:
Probability of Win or Tie vs. Player’s Hand Value (21 not shown because the probability is 100%)
**正如所料,初始玩家手牌值在 12 到 16 之间时,赢/平的概率最低。这些玩家经常被迫陷入“如果我打了我就破产,如果我留下我就输了”的双输局面。**这也解释了为什么初始玩家手牌值为 4 和 5 的概率次低。假设你有一张 5,那么你需要打(没有理由不打)——但是如果你真的打了,那么最有可能的结果是你的手牌总数现在是 15。现在,你已经陷入了和那些初始手牌总数在 12 到 16 之间的玩家一样的困境(如果你击中了,你的下一张牌可能会让你破产)。
我未来帖子的预览
现在我们已经探索了 21 点的风险,接下来是什么?在即将发布的帖子中,我将使用上面生成的训练数据来训练神经网络玩 21 点。这样我就可以检查机器选择什么作为最优策略。
但是今天,让我们看看是否可以用一个简单的启发式方法来提高我们的胜算。回忆两件事:
- 玩家面临的一个主要不利因素是他们被迫先行动(并面临在庄家之前被击败的风险)。因此,赌场的策略是迫使玩家在不确定的情况下行动,希望他们会一掷千金。
- 在我们的模拟器中,玩家根据掷硬币来选择击中或停留,而不考虑他或她的手牌的价值(除非他或她 21 岁)。所以即使他或她 20 岁,仍然有 50%的机会击中。
因此,让我们看看,我们是否可以仅仅通过选择在我们知道没有失败机会的情况下才出手来提高我们的胜算。因此,我们的新决策规则不是抛硬币,而是只有当我们的总牌价等于或小于 11 时才继续击球。
这不是我所知道的最优策略,但很简单。因为它阻止了我们的破产,我们有效地将破产的风险从我们自己转移到了庄家/赌场。
下图比较了我们的新“智能”策略(蓝色)和我们的原始策略(红色的抛硬币策略):
Smart vs. Coin Flip Probability of Win or Tie (bucketed by Dealer’s Card)
哇,永不冒险的简单决定增加了我们全面获胜的几率。而旧的趋势还在,无论庄家亮出什么牌,我们不亏损的概率都增加了。
让我们来看看,当我们按初始手牌值下注时,我们新策略的赢/平概率是怎样的:
Smart vs. Coin Flip Probability of Win or Tie (bucketed by Player’s Initial Hand Value)
看这个情节就更清楚是怎么回事了。除了 12 到 16 之外,我们提高了所有初始牌价的胜算。这些手牌值相对不受影响,因为通过选择留下(为了消除破产的风险),我们使庄家更容易击败我们的手牌(因为庄家只有在手牌值达到 17 或更高时才能停止打击)。
但对于所有其他牌价,我们避免崩盘的策略似乎很有帮助。
我希望你喜欢阅读,并继续关注下一篇文章,我们将看到神经网络是否能够击败我们的天真策略。干杯并记住——永远不要赌你输不起的东西!
我最近的一些帖子,希望你看看:
让我们在新加坡地图上标出 Airbnb 的位置和价格
使用 Matplotlib 以编程方式用 python 标注地图
在地图上绘制数据比你想象的要容易。
让我们通过在新加坡地图上绘制 Airbnb 房屋位置(用颜色和价格区分)来了解一下。为什么?嗯,我喜欢新加坡,但是我每次去都会花一大笔钱在航空旅馆上。
从 Kaggle 下载数据集,然后将其保存到与 jupyter 笔记本相同的目录中。你需要登录 Kaggle 来下载它。
数据集:https://www.kaggle.com/jojoker/singapore-airbnb
该文件是一个 CSV,我们可以很容易地预览熊猫。
import pandas as pd# you may have named it something different
df = pd.read_csv('data-sg-listings.csv')# take a look
df.head()
现在,让我们放弃所有价格超过 500 英镑的房屋,因为离群值会打乱我们的颜色编码。
df = df[df['price'] < 500]
完美。
困难的部分来了。得到新加坡的地图图像,我们最终将在上面绘图。我是这么做的。
我去了 http://www.copypastemap.com 的,输入了将形成一个包围新加坡的边界框的角点的纬度和经度(1.15N,103.5E & 1.50N,104E)。然后我拍了一个截图,用相同的点来表示我的 x 轴和 y 轴的最小值/最大值。
但是省点麻烦,把我的截图截图在下面就行了。然后保存在同一个目录下。
唷。现在是简单的部分。在地图上标出我们的位置。又名。以地图图像为背景,在散点图上绘制我们的点。
import matplotlib.pyplot as plt# import our image
singapore_img = mpimg.imread('singapore-map-3.png')# plot the data
ax = df.plot(
kind="scatter",
x="longitude",
y="latitude",
figsize=(20,14),
c="price",
cmap=plt.get_cmap("jet"),
colorbar=True,
alpha=0.4,
)# use our map with it's bounding coordinates
plt.imshow(singapore_img, extent=[103.5,104,1.15, 1.50], alpha=0.5) # add axis labels
plt.ylabel("Latitude", fontsize=20)
plt.xlabel("Longitude", fontsize=20)# set the min/max axis values - these must be the same as above
plt.ylim(1.15, 1.50)
plt.xlim(103.5, 104)plt.legend(fontsize=20)
plt.show()
看看这个。
对我来说,最困难的实际上是获得一张包含我想要绘制的所有纬度/经度点的图像。多亏了 pandas 和 matplotlib,绘制这些点非常容易。
让我们在画面中弹出这些过滤器!
加快数据可视化
它是数据化的!— Tableau 剧本
Photo by Nicolas Picard on Unsplash
现在,我们又回到了学习有趣的数据可视化概念的旅程中,使用 Tableau、dash-boarding 最佳实践和一些方便的提示/技巧。
在此记录中,我们将经历以下活动:
- 使用细节层次(LOD)表达式来识别是否已经选择了某些过滤器。
- 使用计算字段隐藏/显示消息并突出显示哪些过滤器处于活动状态。
- 使用 Tableau 中的浮动容器来隐藏过滤器,并使它们在满足某些要求时弹出。
这些概念可以在您的 Tableau 旅程中的各种用例中使用。我将参考我创建并发布在我的 Tableau 公共配置文件中的示例仪表板,供您参考。
It’s Datafied! - Tableau Playbook
使用的数据集:样本超市
让我们从如何使用细节层次(LOD)表达式来识别某些过滤器是否被选中开始。
步骤 1:使用 LOD 表达式检查是否选择了地区、州、城市进行过滤
Hierarchical flow to be implemented
由于过滤器必须在我创建的参考仪表板中分层显示,让我们考虑我们必须检查的第一个条件,即是否选择了区域过滤器。将显示一条消息,要求首先选择区域,以便为状态应用过滤器。
一旦区域被选中,即弹出状态过滤器,状态过滤器将被显示。同样,当选择一个州时,会显示城市过滤器。
为了检查第一个条件是否选择了区域过滤器,我们将使用以下公式创建一个计算字段**“未选择区域”**。
计算字段名称:“未选择 c#区域”
注意:如果您向自定义计算字段名称或 Tableau 中的参数名称添加类似 c#或 p#的前缀,以保持它们有组织并与您的其他维度/度量不同,这被认为是一个好的做法。
公式:
SUM([记录数])= SUM({ SUM([记录数])})
公式分解:
SUM([记录数]) 表示当前视图中记录数的总和,即应用区域过滤器时,该计数会相应变化。
SUM({ SUM([记录数])}) 代表固定 LOD,即它将计算数据集中的记录数,而不参考视图中的维度。这也被称为表作用域 LOD 。由于 tableau 不能在同一个表达式中混合聚合函数和非聚合函数,我们必须将其与 SUM([记录数])进行比较,即当前视图中记录数的总和。
除非选择了区域筛选器,并且在当前视图中筛选了记录数,否则这种比较是正确的。
接下来,只有选择了州过滤器,才会显示城市过滤器。因此,为了检查这种情况,我们创建了以下计算字段:
计算字段名称:“未选择 c#州”
公式:
SUM({固定[区域]:COUNTD([状态])})= COUNTD([状态])
公式分解:
SUM({固定[区域]:COUNTD([状态])})
在这里,我们对每个区域的不同状态进行计数,并使用固定的 LOD 对其进行汇总。因为不同的计数是跨每个区域计算的,所以它将不受除了区域之外的过滤器/维度的影响,因为它是固定的 LOD。
COUNTD([State]) 表示当前视图中不同状态的计数,即一旦应用了区域/州过滤器,该计数会相应改变。
同样,我们也将为 City 创建一个计算字段。
计算字段名称:“未选择 c#城市”
公式:
SUM({固定[州]:COUNTD([城市])})= COUNTD([城市])
步骤 2:使用计算字段根据适当的筛选器选择/取消选择来显示/隐藏消息
如您所见,我们有两个平铺窗口,上面显示一条消息,说明一旦选择了层次结构中较高的过滤器,就会显示相应的过滤器。让我们了解它们是如何工作的。
计算字段名称:“c#区域未选择文本”
公式:
如果[未选择区域],则“选择区域以查看状态过滤器”结束
公式分解:
如果没有选择区域,即如果我们在上述步骤中创建的’区域未选择’ LOD 表达式为真,则显示消息。因为我们没有 else 条件,所以在这种情况下不会显示任何消息。
以同样的方式,我们创建另一个计算字段,这一次是用于城市筛选器。
计算字段名称:’ c#状态未选择文本’
公式:
如果[未选择州],则“选择州以查看城市过滤器”结束
步骤 3:创建工作表
所以现在我们到了第三步!太棒了。让我们继续前进,事情会越来越清楚。我们将使用在之前步骤中创建的字段,并创建 3 个工作表:
- 隐藏状态过滤器!
- 隐藏城市过滤器!
- 过滤警报!
隐藏状态过滤器!
所以现在我们将计算字段 ’ c#区域未选择的文本 ’ 添加到文本卡中,并进行适当的格式化。接下来,我们将 LOD 表达式’c # Region Not Selected’添加到 rows 工具架和 filters 工具架上。隐藏标题,如下所示:
我还在工作表中添加了区域过滤器,向您展示它是如何工作的。
确保在从过滤器中选择一个区域后,勾选“c # Region Not Selected”的编辑过滤器选项中的 True 值,并取消勾选 False 选项。
现在,如果我选择一个特定的区域,如下图所示:
- 我们视图中的记录数只针对中心区域进行了筛选,因此***([记录数])SUM({ SUM([记录数])})*** 因此我们的计算字段****‘c #区域未选择’返回一个False****值。**
- 这个假值使文本消失,因为在*‘c #区域未选择文本’*计算字段中不满足我们的条件。
- 由于**‘c #区域未被选择’被用于行货架,由于轴为假*并被过滤,视觉的整个空间也被消除。这是重要的一步,你将在下一节中了解这一步有多重要。*
隐藏那个城市滤镜!
同样的,你可以创建’隐藏那个城市过滤器!'工作表如下所示:
过滤预警!
我希望现在您已经非常清楚 LOD,因此我们将继续使用它们来创建一个计算字段,该字段将显示一个警报,提示控制面板上哪些筛选器当前处于活动状态!是啊,那真的很酷,对吧?
计算字段名称:“应用了 c#筛选器的警报”
公式:
如果不是[未选择 c#地区]和[未选择 c#州]以及[未选择城市]
然后“使用中的过滤器:区域”
else if NOT[未选择 c#区域]和 NOT[未选择 c#州]和[未选择城市]
然后“使用中的过滤器:地区、州”
else if NOT[未选择 c#区域]和 NOT[未选择 c#州]和 NOT[未选择城市]
然后“正在使用的过滤器:地区、州、城市”
否则""
结束
创建一个新工作表,并将该字段添加到文本卡中。
该公式是不言自明的,我将把它留给您来试验,并理解在选择过滤器时计算字段如何返回 True /False。
步骤 4:使用浮动容器在仪表板中添加元素,使过滤器弹出!
唷!做完那些计算和工作表!现在我们开始仪表板开发。
因为在这种方法中我们必须重叠容器,所以我们将使用浮动容器。让我们直接进入流程吧!
Layout for our dashboard
我们将把我们创建的带有文本消息的工作表放在浮动容器中该字段的实际过滤器之上。然后,过滤器将隐藏在另一个浮动可视化后面。
一旦选择了层次结构中较高的过滤器,文本消息就会消失,从而显示出在容器中弹出的过滤器!
在本次演示中,我将使用两个图表 — 堆叠图例过滤器、双轴密度标志图。
你可以在 我之前的博文 中了解到他们,链接也在本文末尾分享了。
我已经将本次演示所需的所有浮动元素添加到仪表板上:
您可以通过对象部分将浮动垂直容器添加到空白仪表板:
状态筛选器是使用应用它的 visual 添加的:
现在,我们将通过按住 shift 键将带有文本消息和状态过滤器的工作表添加到浮动容器中。
确保未选中容器的“平均分配内容”选项。
提示:您可以通过双击宽灰色条来选择和移动容器,选择后该条显示在仪表板元素的顶部。
使用项目层次结构,我们可以将地图可视化拖动到项目层次结构的顶部,然后适当地放置它以隐藏它后面的状态过滤器:
现在,当我们通过启用过滤器动作将过滤器栈用作过滤器时,
瞧啊。状态过滤器从地图后面弹出!
我们已经介绍了一些有趣的新概念,这些概念在您的# DataRockstar Tableau 之旅中可能会非常有用。太棒了,对吧?!
注意:从我的 tableau 公共配置文件下载 Tableau 工作簿后,请务必检查仪表板操作*,当取消选择该地区时,我使用了过滤器操作来重置州和城市过滤器,以避免任何歧义。那是让你作为一个活动进一步探索的!*
总结
- 开发的组件——识别使用 LOD 表达式和计算字段选择的过滤器,显示活动过滤器的文本消息,组合浮动容器中的元素。
- 整合了最佳实践-遵循组织计算字段和参数的命名方案。
- 分享的提示和技巧——如何使用浮动容器隐藏元素,并根据层次结构中的过滤器选择显示它们。
请随意从我的 Tableau 个人资料中下载工作簿,并试用它以获得更好的理解。敬请期待进一步的帖子和快乐的餐桌!
*[## Tableau 公共
随意分享和玩耍
public.tableau.com](https://public.tableau.com/profile/pavneet.singh#!/vizhome/ProfitSalesAnalysisacrossCitiesinUSA/ProfitSalesAnalysisDashboard)* * [## 堆叠图例过滤器、双轴密度标记图和双轴散点图
它是数据化的!— Tableau 剧本系列
towardsdatascience.com](/stacked-legend-filter-dual-axis-density-marks-map-dual-axis-scatter-plot-in-tableau-3d2e35f0f62b)* *[## 概述:详细等级表达式-表格
本文解释了细节层次表达式是如何计算的,以及它们在 Tableau 中的作用。更多信息…
help.tableau.com](https://help.tableau.com/current/pro/desktop/en-us/calculations_calculatedfields_lod_overview.htm)* * [## 操作和仪表板
当源或目标是仪表板时,操作通常具有独特的行为。因为仪表板可以包含…
help.tableau.com](https://help.tableau.com/current/pro/desktop/en-us/actions_dashboards.htm) [## 调整仪表板的大小和布局
创建仪表板后,您可能需要调整其大小并重新组织,以便更好地为用户服务。固定大小…
help.tableau.com](https://help.tableau.com/current/pro/desktop/en-us/dashboards_organize_floatingandtiled.htm)*
让我们对机器学习模型进行欠适应和过适应
构建过度和不足的模型
一位同事最近开始使用术语“欠拟合”来指代命名实体识别(NER)模型,该模型遗漏了它应该标记的实体。
我必须澄清事实。这实际上并不是不合身,但我可以理解有人会有这种印象。
那么什么是适配不足,或者适配过度呢?
让我们训练一些低估和高估数据的模型!
我们先用 sklearn 的“make_classification”函数生成一个数据集。每个数据点将有 2 个特征(所以很容易绘制)和一个标签。
from sklearn.datasets import make_classification# We didn't need to display all params but I like to see defaults
# I've edited some of these
X,y = make_classification(
n_samples=30,
n_features=2,
n_informative=2,
n_redundant=0,
n_repeated=0,
n_classes=2,
n_clusters_per_class=2,
weights=None,
flip_y=0.01,
class_sep=1.0,
hypercube=True,
shift=0.0,
scale=1.0,
shuffle=True,
random_state=None
)# Split examples by class (positive/negative) to give diff colors
pos_feat0 = []
pos_feat1 = []
neg_feat0 = []
neg_feat1 = []for idx,klass in enumerate(y):
if klass == 1:
pos_feat0.append(X[idx][0])
pos_feat1.append(X[idx][1])
else:
neg_feat0.append(X[idx][0])
neg_feat1.append(X[idx][1])# And plot them
import matplotlib.pyplot as plt
plt.scatter(pos_feat0,pos_feat1, c='blue')
plt.scatter(neg_feat0,neg_feat1, c='red')
嘣。我们有数据。
现在,我们将浏览欠拟合和过拟合的定义,然后有意识地选择将欠拟合和过拟合数据的算法。
欠拟合
根据[维基百科](http://Underfitting occurs when a statistical model cannot adequately capture the underlying structure of the data.)。
当统计模型无法充分捕捉数据的底层结构时,就会出现欠拟合。
**翻译:**模型在数据中找不到可靠的模式。这并不意味着没有模式。只是模特找不到。
from sklearn.linear_model import SGDClassifiermodel = SGDClassifier()
model.fit(X, y)# set min and max values for the x and y axes
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
a = np.arange(x_min, x_max, 0.1)
b = np.arange(y_min, y_max, 0.1)# build a grid of each unique combination of x and y
xx, yy = np.meshgrid(a, b)# make predictions for every combination of x and y on that grid
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)# draw the classification boundary
plt.contourf(xx, yy, Z, alpha=0.4)# adds the points from our training data
plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k')plt.show()
完美。该模型在绘制决策边界方面做得很糟糕。它不能使用特征来确定一个例子的类别。吃不饱!
过度拟合
据维基百科。
产生的分析与一组特定的数据过于接近或精确,因此可能无法拟合额外的数据或可靠地预测未来的观察结果
翻译:模型学习输入的例子,但它不能推广到其他例子。
from sklearn.tree import DecisionTreeClassifier
model = DecisionTreeClassifier(max_depth=4)model.fit(X, y)# set min and max values for the x and y axes
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
a = np.arange(x_min, x_max, 0.1)
b = np.arange(y_min, y_max, 0.1)# build a grid of each unique combination of x and y
xx, yy = np.meshgrid(a, b)# make predictions for every combination of x and y on that grid
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)# draw the classification boundary
plt.contourf(xx, yy, Z, alpha=0.4)# adds the points in our training data
plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k')plt.show()
太美了。又一个可怕的模型。它在应该出现的例子周围画出了界限,但是它发现的模式毫无意义,并且可能无法推断出新的例子。
让我们现在拟合数据,只是为了好玩
from sklearn.linear_model import LinearRegression,LogisticRegression
model = LogisticRegression()model.fit(X, y)# set min and max values for the x and y axes
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
a = np.arange(x_min, x_max, 0.1)
b = np.arange(y_min, y_max, 0.1)# build a grid of each unique combination of x and y
xx, yy = np.meshgrid(a, b)# make predictions for every combination of x and y on that grid
Z = model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)# draw the classification boundary
plt.contourf(xx, yy, Z, alpha=0.4)# adds the points in our training data
plt.scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k')plt.title('Underfitting')plt.show()
太好了。不完美。但是比之前的 2 好多了。
这就是了。欠拟合、过拟合和计划拟合。
我们有意选择了一个简单的 2 要素数据集,以便您可以在图表上看到决策边界。
具有数千个特征的真实例子需要一种更数值的方法来测量欠拟合和过拟合。但是我们会留到下一天。
让我们通过建模汽车来理解机器学习中的向量空间模型
带有代码示例
根据维基百科。
向量空间模型或术语向量模型是一种代数模型,用于将文本文档(以及一般的任何对象)表示为标识符的向量,例如索引术语。
Translation :我们将数据集中的每个例子表示为一个特征列表。
wikipedia: the document is a vector of features weights
该模型用于表示 n 维空间中的文档。但是“文档”可以指您试图建模的任何对象。
无论你是否明确理解这一点,你已经在你的机器学习项目中使用了它。
什么是维度?
只是我们建模的对象的一个特征。比如说。
房屋可以有维度:房间数量、销售价格、建造日期、纬度和经度。
人物可以有维度:年龄、体重、身高、发色 _ 是蓝色、发色 _ 是其他。
句子对于每个可能的单词/单词可以有一个维度。
汽车可以有维度:最大速度、加速度、时间和价格。
最终,特性/尺寸是您在特性选择过程中决定的。
让我们在 n 维空间中建模汽车
我们的维度将是:
1。max_speed(最大速度单位为公里/小时)
2。加速 _ 时间(达到 100 公里/小时的秒数)
3。价格(美元)
我挑选了 3 辆车并收集了它们的信息(声明:我对车一无所知……)。
2020 款保时捷卡宴
max _ speed:304 kmph
acceleration _ time:4.4s
售价:9.92 万美元
2018 款特斯拉 Model S
max _ speed:250 kmph
acceleration _ time:3.8s
售价:79990 美元
2020 款宝马 i3s
max _ speed:160 kmph
acceleration _ time:7s
售价:6 万美元
那么向量看起来像什么呢?
T43【保时捷= (304,4.4,99200)
特斯拉= ( 250,3.8,79990 )
宝马= ( 160,7,60000 )
让我们画出这些。
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D
from numpy.random import rand
from pylab import figure# features
X = np.array([
[304, 4.4, 99200],
[250, 3.8, 79990],
[160, 7, 60000]
])# labels
y = ['porsche', 'tesla', 'bmw']# setup our chart
fig = figure()
ax = Axes3D(fig)# iterate on the examples and plot them
for i in range(len(X)):
ax.scatter(
X[i,0],
X[i,1],
X[i,2],
color='b')
ax.text(
X[i,0],
X[i,1],
X[i,2],
'%s' % (str(y[i])),
size=20,
zorder=1,
color='k')# label our chart
ax.set_xlabel('max_speed')
ax.set_ylabel('acceleration_time')
ax.set_zlabel('price')
pyplot.show()
酷毙了。
要看到汽车相互之间的方位有点困难,但是如果我们更有雄心的话,我们可以使用 matplotlib 的“动画”模块从不同的角度查看图表。
如果有 3 个以上的维度呢?
我选择了 3 个连续的特征,因为它很容易绘制。在现实中,我们可能有成千上万的功能,在我工作的 NLP 中,这是典型的情况。
但是有一些心理技巧可以用来想象 3 个以上的特征。就像想象每个人都有自己的酒吧秤。因此,如果我们有 5 个功能,而不是 3 个…
I like Tesla’s 😃
为什么使用向量空间模型?
它使得计算两个对象之间的相似性或者搜索查询和一个对象之间的相关性变得容易。
从上面看,我们的汽车有多相似?
这种相似性计算将忽略一些关键因素(即每个特征的重要性)。
首先我们将缩放我们的数据,然后计算余弦相似度,这是最流行的相似度算法之一。
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import StandardScaler# scale unit variance
scaler = StandardScaler()
scaler.fit(X)
X_scaled = scaler.transform(X)
X_scaled# calculate cosine_similarity
similarities = cosine_similarity(X_scaled,X_scaled)
print(similarities)#=>
[[ 1\. 0.42538808 -0.9284599 ]
[ 0.42538808 1\. -0.73110643]
[-0.9284599 -0.73110643 1\. ]]
好吧!我们可以看到保时捷和特斯拉比宝马更相似。
就这样了,伙计们。
这是对向量空间模型及其用途的快速介绍。虽然你几乎会在任何机器学习项目中接触到这一点,但这不是你需要有意识地思考的事情。如果没有别的,这是一个观察世界的有趣的心智模式。