从零开始为你自己的棋盘游戏创造人工智能-alpha zero-第 3 部分
用 AlphaZero 算法实现 AI 到 EvoPawness(暂名),一个来自我创意的棋盘游戏。
Photo by Maarten van den Heuvel on Unsplash
大家好,欢迎来到**evopheness(暂名)**上制作 AI 的第三部分。在本文中,我们将实现 AlphaZero 算法到游戏中。这篇文章将告诉你一个关于 AlphaZero 和 AlphaZero 实现的简短总结,尽管是简化的。我们将一步一步来做。我们将使用第 1 部分中提到的一些术语,如结果函数、可能的动作和终端函数。
实验还没有完成。训练这款游戏的代理需要很长时间,尤其是单机。我已经训练特工一天了。可惜代理还是不够好。即便如此,它还是学会了一些策略,比如如果骑士能攻击国王,就用骑士连续攻击国王。
我停止训练代理让我的电脑休息。我怕我的笔记本电脑强行训练一天以上就坏了。我还限制了 AlphaZero 的超参数,以加快训练时间。很抱歉我做这个实验的资源有限😿。
Just a random cat. Source : https://pixabay.com/en/cat-sad-cute-small-sweet-pet-3266673/
即使实验没有完成,我也会试着写下我是如何在这个游戏上实现 AlphaZero 的。我希望它能给那些想学习强化学习算法 AlphaZero 并在游戏中实现它们的人一点启发。
我对游戏规则做了几处改动。这个游戏仍然是决定性的。
这篇文章的目标读者是对人工智能和设计游戏感兴趣的人。如果你不是其中之一,当然你还是可以看到这篇文章。我希望通过发表这篇文章来提高我的写作技巧,并且内容对你有益😄。
振作起来,这将是一篇长文。它有 25 分钟的阅读时间!
概述
- 贮藏室ˌ仓库
- 关于 AlphaZero 的简要描述
- 改变规则
- 步伐
- AlphaZero 的组件
- AlphaZero 的实施
- 吸取的教训
- 结论
- 编后记
贮藏室ˌ仓库
如果你开始阅读这篇文章的游戏进展,这里是资源库:
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness)
我已经向存储库添加了几处更改:
- 添加 AlphaZero 实现
- 重新格式化模块或文件夹结构
- 更改动作表示键。
- 更改游戏模型和控制器以匹配新规则
注意:现在,我建议你不要试图训练模型,直到我重构和清理代码。还有,代码仍然超级乱,我会尽快清理和重构。在下一个星期六,我会努力做到这一点。
**编辑(2018 年 3 月 12 日)😗*我已经清理了代码。现在我们有 Config.py 来编辑配置。关于如何启动程序,请参见main.py -h
。详情等我明天编辑完README.md
。
编辑 2 (10/12/2018) :我已经推送了更新的README.md
关于 AlphaZero 的简要描述
Source : https://en.chessbase.com/post/the-future-is-here-alphazero-learns-chess
AlphaZero 由 Deep Mind 创建,发表在以下论文中[来源 4]。它的特别之处在于,它可以在 24 小时的训练下击败国际象棋和松木的最佳 AI。它使这个算法成为当时 AI 游戏上最好的。要了解更多信息,你可以看看这篇关于这个算法有多强大的文章。
它也没有人类的专业知识作为输入(游戏规则除外)。它从零开始学习,成为最好的人工智能游戏程序,击败了当时棋盘游戏中最好的人工智能。
我还发现算法非常简单,但令人惊讶。该算法将利用当前知识探索有希望的可能路径。搜索到的路径将判断当前路径是否有利。其有利程度的分数被降低,并被用作是否应该再次探索这条道路的考虑因素。
在思考未来可能性的探索之后,采取探索最多的行动。这意味着,如果经常探索这条道路,这个行动可能是好的。
在游戏结束时,评估它选择的路径是否与游戏的结果相匹配。它告诉我们,在探索未来可能的道路时,知识是否误判了选择的道路。知识会根据比赛结果更新。
AlphaZero 要求博弈具有完全信息(博弈状态对双方都是完全已知的)和确定性。既然这个游戏两者都有,那么 AlphaZero 算法就可以用在这个游戏上。
在本文中,我们将简化本文中使用的架构。我们将基于我读过的文章使用一个简化的 AlphaZero 实现。在大卫·福斯特【来源 2】使用的实现中,他使用了 4 个连接到策略头和值头的剩余层。我使用了他的架构并改变了一些超参数。我还在[Source 1]中看到了 AlphaZero 的实现,并修改了实现以适应这个游戏。我遵循了那篇文章中的蒙特卡罗树搜索实现,并将实现从使用递归改为结构化数据树。
这些文章中使用的实现与本文一致,只是跳过了一些部分。我跳过了几个步骤,比如v resignation
。在这两篇文章中,它们没有实现堆叠状态。在本文中,我实现了堆叠状态,并使用 140 个平面作为模型的输入。在本文中,AlphaZero 的实现还没有通过使用虚拟丢失来实现多线程 MCTS。
改变规则
- 符文将在 3 个不同的位置出现。有(在 y,x 轴上)(4,0),(4,4),和(4,8)。在(4,0)产生符文将会增加 2 点攻击点。在(4,4)产生的符文将提高 2 点生命值。在(4,8)处产生的符文将会增加 1 步点数。
- 当符文产生时间(每 5 回合)不会产生符文时,棋子所占据的符文位置。
- 步数、生命值和攻击点数是有限制的。步数 3,生命值 20,攻击点 8
步伐
本文将按照以下顺序实现 AlphaZero:
- 定义模型输入的状态表示。我们使用堆叠状态。
- 定义游戏的动作表现。
- 决定哪一方将用于代表所有玩家的状态(黑色或白色),并定义如何更改该方。
- 定义一些可用于增加模型输入的相同状态
- 定义奖励函数
- 实现用于输出策略评估的神经网络的体系结构
- 实现蒙特卡罗树搜索
- 添加 duel 函数来评估最佳模型。
AlphaZero 的组件
在我们进入这一部分之前,我建议如果你没有读过第一部分,读一读。它将涵盖游戏规则。
表示状态
在本节中,我们将看到神经网络输入的表示状态。神经网络的输入是代表状态的图像堆栈。实现中使用了 28 个输入特性。这里我们就用 5 步历史**(注:**在 AlphaZero 的论文中,步历史的个数是 8。这里,本文将尝试不同的数字)。从我们使用的步骤历史数来看,这意味着有 140 个输入特征(28 X 5)。
历史将保存前一回合的状态表示。如果我们使用 5 步历史,假设状态在 T 时刻,我们将获得 T、T-1、T-2、T-3、T-4 状态,这些状态将被堆叠并成为神经网络的输入。
以下是输入特征:
4-10,13 号使用二进制特征,而其他使用频率特征。
因为,棋盘是 9×9,如果我们有batch_size
作为神经网络输入的总实例,我们就有(batch_size, 9, 9, 140)
形状作为神经网络的输入。因此,我们有 4 维数据作为输入。
对于代码,可以在下面的源代码中看到get_representation_stack()
函数:
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/util/stacked_state.py)
在源代码中,我们将使用包含我们在第 1 部分中定义的元素的AIElements
类。我们使用deque
作为我们的数据结构(像堆栈一样)。
在我们获得神经网络的输入表示之前,我们将把状态(我们在第 1 部分定义的状态表示)堆叠到一个具有期望的最大长度的deque
数据结构中(在本文中,我们将其设置为 5)。然后我们处理deque
,并将其转换为神经网络的输入表示。
动作表示
游戏中有 5 种动作可供选择。有激活、提升、移动、攻击和跳过。
对于激活动作,我们需要选择想要激活的棋子。我们需要棋子的坐标。因此,我们有 81 (9 x 9)个不同的独特激活动作。
**注意:**我们有一个不同于第 1 部分中所述的动作键表示,新的表示如下:
Action Key Representation for the activate action: a*y,x
y : pawn's axis-y
x : pawn's axis-x
example a*1,0
对于升级动作,我们需要选择想要升级的棋子,然后选择可能的选项来升级该棋子。我们需要棋子的坐标。我们有 9 x 9 种不同的独特行动来选择可能的棋子。有 4 种升级兵类型(女王、车、主教、骑士),因此有 324 种(9 x 9 x 4)独特的升级方式。
Action key representation for the promote action : p*y,x*choice
y : pawn's axis-y
x : pawn's axis-x
choice : promote choice, K for Knight, R for Rook, B for Bishop, and Q for Queen
example p*3,3*Q
**对于攻击和移动动作,**在这个游戏中,我们有 7 种类型的兵。移动的方向已在第 1 部分中定义。在第一部分中,我们将攻击和移动作为单独的动作。在本文中,我们将把攻击动作和移动动作合二为一(它们没有重叠,所以我们可以合并)。我们可以看到士兵、车、主教和国王的移动方向是女王的子集。它在 N、NE、E、SE、W、SW、W 和 NW 方向上垂直、水平和对角移动。只有骑士的招式不同。它在指南针的各个方向上呈 L 形移动。总之,我们有两种类型的移动,女王和骑士。
我们采取行动有两个步骤:选择棋子和根据可能的行动选择棋子的合法移动。在这种情况下,我们有 81 (9 x 9)个不同的选择棋子的动作。然后为了选择合法的移动,我们有 8 种不同的骑士移动类型和 24 种(8 x 3)女王移动类型(注意:我们有 3 作为步点的限制,所以女王移动可以由 24 移动类型组成)。为攻击和移动动作选择合法移动,可以进行的唯一动作总数为 2592 (81 x 32)。
Action Key Representation for the activate action: m*y1,x1*y2,x2
y1 : selecting the pawn in axis-y
x1 : selecting the pawn in axis-x
y2 : direction of the move in axis-y
x2 : direction of the move in axis-xExample m*0,1*2,1 (means that it will move from (0,1) to (2,2)
如果玩家不能做任何事情,跳过。
The action key representation for skip is skip
唯一动作的总数是 2998 (9 x 9 x 37 + 1)
动作表示用于编码概率分布,该概率分布用于随后进行蒙特卡罗树搜索(MCTS)时选择动作。稍后,在该状态下不可能做的动作被屏蔽,并且将概率设置为 0,并且重新归一化可能动作的概率分布。
有关如何生成所有可能的动作的实现,请参见存储库。我使用scikit-learn
库中提供的LabelEncoder
和OneHotEncoder
将动作编码到一个热编码器中。
一个热编码器类别(见fit()
):
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/util/action_encoder.py)
生成所有独特的动作(见action_spaces()
功能):
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/util/alphazero_util.py)
决定哪一方将用于代表所有玩家的状态(黑色或白色),并定义如何更改该方。
在 AlphaZero 的论文中,我们需要将棋盘的视角定位到当前玩家。我们必须决定哪一方对双方都有利。我选择白色作为双方玩家的视角或观点(POV)。所以如果当前回合是黑人玩家,我们会让黑人玩家视角变成白人玩家视角。如果当前回合是白牌玩家,我们不会改变玩家视角。
我们如何做到这一点?
如果这是一盘棋,那就好办了。只需颠倒棋子的颜色。在这场比赛中,我们不能这样做。国王被固定在那个位置,不能移动。所以,颠倒棋子会使状态无效。那么如何才能做到呢?
我们需要镜像或反映每个棋子的位置。之后,反转所有棋子的颜色。
下面是伪代码:
def mirror(self, pawn):
pawn.y = pawn.y - 4 # 4 is the center position of the board
pawn.y *= -1 # after we move the center to 0, we mirror it pawn.y += 4 # Then we move the center point to 4
pawn.color = inverse(pawn.color)
我们对所有的兵都这样做(包括国王)。我们已经反映了棋子的位置。
Example of the original state and the mirrored one.
当我们通过改变棋子的位置来改变棋盘的视角时,我们也需要改变动作。如果动作是移动或攻击,我们需要镜像动作的坐标和方向。这类似于改变棋子的位置,我们将通过操纵 y 轴镜像坐标来改变动作的坐标。对于方向,我们只需要将 y 轴乘以-1。
例如:
{} = * means multiplication in this bracket
original = a*2,3
mirror = a*{(2-4)*-1+4},3 = a*6,3original = m*1,0*1,0
mirror = m*{(1-4)*-1+4,0}*{1*-1},0 = m*7,0*-1,0
这就是我们改变玩家视角的方式。
有关实现,请参见源代码中定义的所有函数:
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/util/state_modifier_util.py)
它本质上和伪代码一样,但是是用面向对象的方式。我们将改变所有棋子的属性。动作的镜像也在源代码中定义。
定义一些可用于增加神经网络输入的相同状态
我们应该定义一些相同的状态,用于神经网络的输入,如果游戏有它的话。随后,它可用于增加神经网络的输入。此外,在本文中,相同的状态将被用于评估 MCTS 的叶节点中的状态,其中它将被均匀地随机选择。相同的状态通常是原始状态的二面角反射或旋转。我认为,这个组件的目的是使培训更快,并确保包括应该有相同情况或评估的状态。
不幸的是,这个游戏没有相同的状态。所以我们不能在这个游戏中使用这个组件。
定义奖励函数
我们将使用奖励函数来告诉代理人最终的结果是赢还是输。由于我们使用蒙特卡罗树搜索,我们将在终端状态调用奖励函数。然后就会变成我们要优化的神经网络的输出。
奖励函数是我们在第 2 部分定义的效用函数。我们将把该值归一化到{-1,1}的范围内。下面是高级伪代码实现:
def reward_function(state, player):
if state.win(player):
return 1
else
return -1
由于只有在状态处于终态时才会调用奖励,所以这叫稀疏奖励。神经网络将被训练以评估和预测当状态不是终点时的奖励。
具体实现,可以看State.sparse_eval()
函数。第 1 部分定义了如何调用终端状态。
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/model/state.py)
神经网络体系结构
在本节中,我们将创建 AlphaZero 中使用的神经网络的架构。在本文中,他们使用了 20 个剩余块,后跟策略头和值头。他们使用它是因为它是当时计算机视觉任务中最先进的深度学习架构。当然,我们不会用那个。尤其是对我这样一个预算很低、资源很少的人来说😢。
相反,我们将使用简化的架构。我们使用[Source 2]中定义的架构,但做了一些修改。这是它的架构:
Implementation of our neural network. x = 4, we use 4 residual layers.
因此,我们将有 4 个剩余块,2 个 CNN,后跟一个策略头和值头。
超参数也简化了。根据我的 GPU 减少了每一层的输出单位。我觉得那篇文章用的超参数就够了。因此,我们将遵循那篇文章中使用的超参数,只做一点点改变(比如稍微增加或减少输出单位)。
神经网络的输入是我们上面定义的状态表示。
神经网络有两个输出,标量特征 v 和移动概率矢量 p 。神经网络的输出范围对于 v 为{-1,1},对于 p 为{0,1}。这就是为什么我们将 tanh 用于 v 的激活功能,将 softmax 用于 p 的激活功能。
神经网络将最小化以下目标函数:
Loss/Objective function excluding the regularization
其中vθ(st)
是数值头的输出,一个标量,它评估当前状态的情况,而 pθ(st)
,策略头的输出,是来自状态st
的预测策略。
vθ(st)
会被训练得和zt
一样接近,这是一个玩家相对于所选视点(POV)的视角,游戏的最终结局。在我们的例子中,视点是白色玩家。zt
根据游戏结果,值可以是-1、0 或 1。vθ(st)
将被训练计算当前状态的评价。
πt 是来自状态 st 的策略的估计。我们还需要训练神经网络的参数,以使 pθ(st)
与πt
足够接近。pθ(st)
将是一个概率分布的向量,它告诉我们值越高,行动越好,被选中的机会越高。它会被训练得和πt
一样近。如何获得πt
将在下一节中定义。当然,它还需要与所选视点的视角相同。
优化程序正在以定义的学习率使用 Adam 优化程序。
因此,总的来说,我们将尽量减少对当前状态的评估和当前状态的政策的预测误差。设batch_size
是我们的神经网络的总输入实例,状态表示的输入形状是(batch_size, 9,9,140)
。有两个输出,策略头和值头。策略头有(batch_size, 2998)
(唯一动作的总和),值头有(batch_size, 1)
形状。
之后,在蒙特卡洛树搜索模拟中使用神经网络模型来评估和预测状态的策略(策略评估)。该模型将被优化以最小化每集结尾的损失。
有关实现,请参见下文(类别PawnNetZero
):
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/alpha_zero/deep_net_architecture.py)
蒙特卡罗树搜索
Source : mcts.ai
在我们深入蒙特卡洛树搜索之前,我建议在这里阅读关于 MCTS 的简要描述。
我们将使用蒙特卡罗树搜索来提高策略估计质量(策略改进)。这将是预测给定状态下代理的动作的组件。MCTS 用于模拟游戏,这是一种自学式游戏(代理扮演两个轮流游戏的玩家)。对于每一集(一个完整的游戏)的每一步,MCTS 被模拟直到给定的模拟次数。它也是一种搜索算法,像我们以前使用的 minimax,但 MCTS 不会扩展所有可能的操作,而是使用引导式“启发式”来确定要扩展的节点。
MCTS 中的树将由代表板配置的节点组成。存在于每个节点之间的有向边表示状态的有效动作。边不同于极大极小树搜索,不仅要保存动作名称,边还有几个参数,每次模拟都会更新。
该边将包含我们将更新的几个参数:
**Q(s,a)**
:对状态s
采取行动a
的期望报酬或平均报酬,它将在备份步骤中更新。这将是由神经网络产生的所有预测的vθ(st)
的评估或奖励的平均值,或者是叶节点(其是节点s
的后代)中终端状态的实际奖励(-1,0,1)。**N(s,a)**
:状态**s**
下模拟动作**a**
的次数**P(s,a)**
:在状态s
中采取行动a
的估计概率,这是由神经网络的模型产生的策略。
而节点将包含几个参数:
**N(s)**
:模拟进入该状态的次数(s
)。它等于状态s
中每一个可能动作a
的N(s,a)
之和。
在每一集的开始, MCTS 用单个根节点初始化。根节点在初始化时也作为叶节点。从这个根开始, MCTS 将扩展该树,直到达到模拟的极限数量。
初始化树后,在 AlphaZero 中进行 MCTS 有 4 个步骤:
- 挑选
- 展开并评估
- 支持
- 玩
步骤 1–3 根据模拟次数进行迭代,然后执行步骤 4。
挑选
MCTS 中的模拟将在根节点(s0
)开始,并且如果模拟在时间步长 l 遇到叶节点sL
则结束。在这些时间步长的每一个中,对于在扩展和评估步骤中已经扩展的每个节点,根据每个节点的边中的参数选择动作。这里,我们将选择状态s
中的动作 a,其具有最高的U(s,a)
,使用 PUCT 算法的变体的置信上限。
Upper Confidence Bound
其中,cpuct 是确定勘探级别的超参数。N(s,b)
之和等于N(s)
。
如果s
是s0
(根节点),P(s,a)
变为 P (s, a) = (1 — e)*pa + e*ηa
其中η
是使用狄利克雷噪声和所选参数的概率分布,e
是 0.25。这将使探索尝试根状态中的所有移动。
这个步骤将一直进行,直到找到叶节点。如果找到了叶节点,则调用展开和评估步骤。
展开并评估
如果当前节点是叶节点,将执行此操作。我们将使用我们在上面定义的表示来评估状态sL
,作为神经网络的输入。它将输出策略和当前状态的评估值。
输入会以均匀概率分布随机变换成任意相同的状态(可以选择不变的那个)。因为我们在这个博弈里没有,状态不会转换,神经网络的输入永远不变。所以在**evopheness(临时名)**的实现中,函数 di
的输出将返回**sL**
而不转换形式。
输出是策略**p**
和状态sL
的值**v**
,如前一节所述。如果p
是转换状态的动作,那么这个动作应该转换回原始状态的方法。
在**p**
**、**中,无效动作概率将被屏蔽,有效动作将被归一化,这样向量将和为一。
然后,用包含该状态下所有可能动作的边来扩展叶节点。每个边和叶节点参数被初始化为:
N(sL,a) = 0, Q(sL,a) = 0, N(s) = 0, P = p, P(sL,a) = pa
Where **a** is a possible action in the state sL.
支持
在我们展开叶节点(sL
)后,该参数将被反向传递到所有父节点,直到根节点,这是通过每一步t ≤ L
完成的。这些参数将更新如下:
Q(st,at) = (Q(st,at)* N(st,at) + v)/ (N(st,at) + 1)
N(st,at) = N(st,at) + 1
N(st) = N(st) + 1
如果st
的玩家与sL
不同,请注意v = v * -1
。比如:st’
s 转黑,sL’
s 转白。由于这是一个零和游戏,对面玩家(v
)的评价将是当前玩家的负面。
执行备份步骤后,如果尚未达到最大模拟次数,则从根节点再次执行选择步骤。
这些步骤将被重复,直到达到模拟的最大数量。如果已经达到,执行以下步骤:
玩
搜索结束时,应根据模拟时更新的参数选择根位置 s0 中的动作**a**
。动作概率给定根状态(πa|s
)的选择与模拟时计算的访问次数N(s,a)
的指数成比例。
我们将用下面的公式计算所有动作的策略:
其中τ
是用于控制探测程度的温度。当游戏中的回合或步数低于 30 时,τ
设置为1
,否则无穷小。
MCTS 的高级伪代码实现如下:
实现可在此处找到:
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/alpha_zero/mcts.py)
请注意,存储库中的实现会有所不同,但目标是相同的。在存储库中,我在’【T6]'函数中包含了选择、扩展和评估步骤。
竞技场
Source : https://pixabay.com/en/knight-sword-fighting-middle-ages-2551859/
为了保持模型的质量,我们必须确保所使用的模型是最好的。为此,AlphaZero 将比较当前最佳模型和当前模型的质量。做起来相当容易。我们需要两个互相争斗的特工。
这些模型通过选择的 max 模拟和max_step
限制来互相坑**n**
回合,以防止游戏无限循环而使终端状态不可达。这将返回决定最佳模型的分数。**n**
可以由任意数字填充。
如果当前模型以选定的优势胜出(论文中为 55%),则最佳模型将被当前模型取代。最好的模型用于下一集。
如果最佳模型获胜,则最佳模型仍将用于下一集。
我们将启动两个 MCTS,一个使用最佳模型神经网络,另一个使用当前神经网络。播放器的颜色可以自己决定(比如:白色是最好的型号,黑色是当前型号)。
这是伪代码
实现可以在这里找到(fight_agent()
函数):
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/alpha_zero/train_module.py)
就是这样。我们已经为 AlphaZero 定义了所有的组件。现在,我们将连接所有已定义的组件,并执行 AlphaZero 算法。
AlphaZero 的实施
在我们定义了将用于 MCTS 的所有组件之后,让我们来总结一下。
基于我们定义的组件实现 AlphaZero 的步骤如下:
- 生成所有独特的行动,可以用于每个球员。
- 创建神经网络模型,该模型将用于根据生成的独特行动的输入在 MCTS 中进行评估。模型中的策略头将通过唯一动作的总数来调整其输出形状。
- 创建结构数据
deque
,该数据将用于保存关于每次自我游戏结果的信息,并将用于神经网络的输入。必须定义deque
的最大长度。 - 对于每一集,我们创造新的 MCTS 实例,通过自我表演来训练我们的特工。我们将在下面看到这个步骤的更多细节。
- 自弹结束后,根据
deque
中自弹产生的数据训练神经网络模型。deque
中的实例总数受限于定义的deque
的最大长度。 - 在我们训练模型之后,我们将检查当前模型是否比当前最佳模型更好。如果是真的,那么把最好的模型换成当前模型。最佳模型将用于下一集的评估。
每集模拟
当该步骤正在进行自我表演时,我们将进行如下步骤:
首先,我们需要初始化游戏状态和 MCTS。然后,执行以下操作:
首先,我们将参数填入 MCTS ( self_play()
),然后我们得到根上的动作概率(play()
)。我们用动作、状态和玩家信息填充deque
。在状态到达终端或最大步骤后,我们最终将奖励信息添加到deque
中。
在我们在deque
上填充奖励之后,我们将把deque
的内容附加到全局deque
上,这将用于训练神经网络。
self_play 函数是一个 MCTS 模拟器,其工作方式如下:
就是这样,所以 MCTS 的每一次模拟都会模拟到找到叶节点为止。如果已经找到,那么进行扩展和评估。如果没有,进行选择。
就这样,我们已经定义了如何为游戏创建 AlphaZero 实现。
对于实现,您可以在存储库中的 train_module.py 上看到fit_train()
和。
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/reinforcement_learning_train/alpha_zero/train_module.py)
要运行训练,从一开始就使用main_from_start.py
进行训练。而main_from_continue.py
要从检查站训练。目前,我建议在我重构和清理代码之前,不要尝试训练模型。我计划下周六做这件事。
下面是main_from_start.py
的代码
自助项目 Evo Pawness(临时名称)。通过在…上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献
github.com](https://github.com/haryoa/evo-pawness/blob/master/main_from_start.py)
吸取的教训
在实现 AlphaZero 的过程中,我学到了一些经验。
- 我已经运行了代码来训练模型。我明白了,一开始,很难获得一集的输赢。它通常以平局结束(达到最大步数)。我的结论是,我设置的最大模拟是不够的。我需要更高的 MCTS 最大模拟。我也尝试过另一种方式来解决。我试图通过让代理总是攻击敌人来破解 MCTS 的模拟。如果你不清楚 MCTS 中的
greed
变量是什么,对于我设置的 1/8 最大集和选择的最小步长,模拟将总是使攻击动作和促进动作在置信上限上比其他动作有更大的 Q。当点蚀两个模型时,此功能将被禁用。我需要找到一个更好的方法来解决这个问题。 - 我需要实现模拟 MCTS 多线程。单线程仍然可以使用,但是非常慢。因此,在下一部分,我将尝试实现多线程 MCTS。
- 我还没有调整神经网络和 MCTS 的超参数。所以,我还是不知道该用多少层残留。目前,我使用 4 层残留层。我没有调,因为训练很慢😢。
- 一定要通读这篇论文。我已经修改了几次代码,因为我跳过了论文中的某些部分。
结论
在本文中,我们已经构建了将用于实现 AlphaZero 的所有组件。我们还实现了 AlphaZero 算法。这篇文章还没有告诉 AlphaZero 的结果,因为训练还没有完成。
本文仍然使用单个 GPU 来训练模型。它还使用单线程来运行 MCTS 的模拟。所以,训练会很慢。
编后记
Exhausted Source : https://pixabay.com/en/cat-cat-face-sleep-exhausted-1551783/
哇,看看我的 25 分钟阅读时间文章😆。最后,文章完成并发表。感谢我读过的几篇文章,让我可以实验 AlphaZero 算法,理解算法。
由于培训尚未完成,我计划创建下一部分,将重点放在改进和实施 AlphaZero 的结果。不幸的是,由于我的资源有限,我担心这个项目会被搁置,直到我得到一台新的电脑来试验它。嗯,你看,我失业了(有几个原因),所以我这里的钱有限😢。
我会试着修复混乱的代码,让它在下周更容易执行。目前,当程序运行时,代码非常混乱,而且非常冗长。
我欢迎任何可以提高我自己和这篇文章的反馈。我正在学习写作和强化学习。我真的需要一个反馈来变得更好。请确保以适当的方式给出反馈😄。
哦,我答应在最后部分告诉你项目结构和 GUI 的细节。抱歉,我忘记写了😅。如果我有时间,我会把它写在我的下一篇文章上。
对于我接下来的几个项目,我将专注于 NLP 或计算机视觉任务。我将写一些关于在这些任务中使用 GAN 的内容。我想学习 GAN 并实现它,因为它是目前深度学习的热门话题。
如果你想从我这里得到另一篇这样的文章,请鼓掌这篇文章👏 👏。这将提升我写下一篇文章的精神。我保证会做出更好的关于 AI 的文章。
在我的下一篇文章中再见!
Source : https://cdn.pixabay.com/photo/2017/07/10/16/07/thank-you-2490552_1280.png
系列文章
第 1 部分:从零开始为你自己的棋盘游戏创造人工智能——准备——第 1 部分
第 2 部分:为你自己的棋盘游戏从头开始创造人工智能——Minimax——第 2 部分
第 3 部分:为你自己的棋盘游戏从头开始创建人工智能
来源和致谢
web.stanford.edu](https://web.stanford.edu/~surag/posts/alphazero.html) [## 【来源 2】如何使用 Python 和 Keras 构建自己的 AlphaZero AI
medium.com](https://medium.com/applied-data-science/how-to-build-your-own-alphazero-ai-using-python-and-keras-7f664945c188)
【来源 2】作者大卫·福斯特
【资料来源 3】西尔弗、大卫等《在没有人类知识的情况下掌握围棋游戏》。性质 550.7676 (2017): 354。
[资料来源 4] Silver,David,等人《用一般强化学习算法通过自我游戏掌握国际象棋和日本象棋》 arXiv 预印本 arXiv:1712.01815 (2017)。
感谢 Thomas Simonini 建议我继续将 Deep Q 网络应用于基于政策的学习。
从头开始为你自己的棋盘游戏创造人工智能——Minimax——第 2 部分
用 Minimax 算法实现 AI 到 EvoPawness(暂名),一个来自我的想法的棋盘游戏。
“chess pieces on board” by Felix Mittermeier on Unsplash
嗨,生活怎么样?欢迎来到制作**evopheness(暂名)**桌游项目系列文章的第二部分。在这里,我们将实现如何将人工智能添加到游戏中。在本文中,我们将重点介绍一些经典算法的实现。是极小极大和阿尔法贝塔修剪极小极大。我们将回顾我们在上一部分所做的,以及我在关于棋盘游戏(GitHub)的代码库中所做的。
[Image 0] Source : https://pixabay.com/en/drop-of-water-water-liquid-1004250/
在我们深入讨论如何实现它之前,我要告诉你,这一系列文章将一步一步地讲述如何创建棋盘游戏。因此,我将尽可能详细地描述我是如何设计和编写游戏、GUI 和 AI 的。我想分享一下我的经验。所以,我会一步一步来,告诉你我创造人工智能的成功和失败的尝试。
我们还没有介绍 GUI。稍后,我将撰写关于如何制作和设计 GUI 的文章。现在,我们将跳过 GUI,先做 AI 部分。
这篇文章的目标读者是对人工智能和设计游戏感兴趣的人。如果你不是其中之一,当然你还是可以看到这篇文章。我希望通过发表这篇文章来提高我的写作技巧,并且内容对你有益😄。
概述
- 贮藏室ˌ仓库
- 前一部分概述
- 评估和效用函数工程
- 极大极小算法及其实现
- A-B 剪枝极大极小算法及其实现
- 结论
- 编后记
贮藏室ˌ仓库
这里是仓库
在 GitHub 上创建一个帐户,为 haryoa/evo-pawness 的发展做出贡献。
github.com](https://github.com/haryoa/evo-pawness)
我已经对存储库做了一些修改
- 平衡游戏。我和朋友玩过几次,以前的游戏规则真的很不平衡。
- 用 PyQt5 库实现图形用户界面
- 为了这篇文章,改变规则使游戏确定。下一部分会有所改变。
- 添加极大极小值、AB 修剪极大极小值和随机代理
- 重新格式化文件夹的结构
如果您想在图形用户界面(GUI)版本中播放它,请在您的命令行界面(CLI)中使用pip install PyQt5
命令安装 PyQt5。
前一部分概述
在前一部分中,我们已经定义了 AI 算法要使用的一些组件。我们已经陈述了游戏的规则、状态表示、结果函数、动作、结果函数和最终测试。我们也实施了这些措施。
在本文中,让我们利用之前实现的功能来制作一个代理,它可以通过看到对手可能的动作来选择动作。
评估和效用函数工程
让我们回顾一下,效用函数为玩家定义了游戏在终端状态下的最终数值。数值公式是我们定义的。评估函数为玩家定义了来自给定状态的期望效用数值的估计。这个函数在游戏还没有结束的时候被调用。接收状态和玩家的输入,并输出一个表示玩家状态的结果和合意性的数值分数。负的分数意味着玩家处于劣势地位。
没有任何规则来定义评价函数和效用函数。因此,我们需要考虑如何制定它。该函数应该区分玩家是否处于优势地位。
这部分真的很重要,因为这个功能将定义我们代理人的聪明度。
这是我想出的公式:
效用函数
在效用函数中,我们将把效用定义为你想要的高度,并且应该高于我们将在此之后定义的评估函数。
我们在前面已经说过,我们的终态有两个条件:
- 当一个国王死了
- 当玩家的所有兵都死时
在这里,如果到达终端状态,它将返回-120(负表示不利),否则返回 120。
评价函数
在评估函数中,我们将通过考虑兵的 hp、atk、步数和死亡状态来制定它。每个属性的权重为:
- HP 的重量= 0.3
- ATK 的重量= 0.1
- 台阶重量= 0.1
- 死者棋子的重量= 10
- 国王生命值的重量= 1
我们给死者的棋子 10 的权重,因为在这个游戏中失去一个棋子是非常糟糕的。因此,我们希望通过给它一个高权重,人工智能将避免其棋子死亡。金在这里真的是一个重要的棋子。所以,我们会给它一个更高的分数。
我们将在调用类State
中的评估函数的AIElements.evaluation_function()
函数中定义效用和评估函数。
极大极小算法及其实现
最小最大值是一个决策规则,它模拟玩家的决策,以找到玩家的最优移动。它假设对手也会选择最优策略。迷你剧里有两个演员。是最大化器和最小化器。最大化器将尽可能搜索最高分,而最小化器将尽可能搜索最低分。每个玩家将看到对手的最佳移动,并为他/她得到最好的移动。通常,搜索用树结构数据表示。
[Image 0.5]
这个例子说明了极大极小算法
[Image 1] Source: AI: A Modern Approach Book
看图 1,Max 会输出 B,C,D 动作。首先,最小化者会看到导致 B 的可能行动,得到最低分 min(3,12,8) = 3
。然后对 C 和 d 也这样做。之后,最大化器将选择最高分。也就是max(3,2,2) = 3
。
在我们的棋盘游戏中,算法不会扩展状态/节点,直到终止状态。它将有内存问题或需要很长时间来处理。我们应该通过定义限制应该扩展的节点的max_depth
来为我们的搜索算法定义一个截止点。它将调用计算条件的启发式的评估函数。
[Image 1.5]
让我们将它实现到我们的evopheness(临时名称):
我们将为 Minimax 算法定义一个类,它接收max_depth
和player_color
作为它的输入。player_color
是指出这里谁是最大化者的参数。
让我们看看 Minimax 类:
如你所见,Minimax 代理将使用递归搜索进行深度优先搜索。它将输出分数(best_value
)和最佳动作动作键名。如果轮到最大化者,它将得到最大的分数,如果轮到最小化者,它将得到最小的分数。它将递归搜索,直到达到终端状态或最大深度。
我们打乱列表,让代理随机选择得分相同的动作。
如果你想知道action_target
的型号,那就是str
型。记住我们的游戏动作是一个dict
类型,它保存了动作信息和动作键名。例如:
‘p1a2’:
{‘action’: ‘activate’,
‘pawn_atk’: 1,
‘pawn_hp’: 5,
‘pawn_index’: 2,
‘pawn_step’: 1,
‘pawn_x’: 4,
‘pawn_y’: 1,
‘player_index’: 1},
测试代理
我们将使用深度= 4 的MinimaxAgent
转到 controller/game_controller.py 中的第 52 行,将其改为
self.ai_agent = MinimaxAgent(max_depth=4, player_color=0)
[Image 2] I exit the game because I cannot stand waiting the AI’s move
用你的命令行界面运行游戏
python main_gui.py
人工智能会优先到达符文。然后,激活它的棋子。然后先攻击我们的兵。如果可能的话,人工智能会把它的兵进化成更高级的类型。当然也有一些瑕疵,比如让我们的士兵攻击他们的国王。也许有更好的评价函数可以让 AI 保护国王。
如果你尝试过人工智能,你会发现选择动作所消耗的时间非常长。让我们看看:
从上面我们可以看到,随着游戏的进行,expanded_node 也在增加,因为可能的动作数量。随着扩展节点的增加,算法的时间往往会增加。我们可以看到 24 号弯的时间是 21 秒。谁想等那么久?
这是该算法的弱点,因为它将搜索所有可能的状态,直到期望的截止或终止状态。幸运的是,我们可以有效地切割扩展的节点。我们将使用一种叫做修剪的技术来计算正确的极大极小决策,而不需要查看每个节点**。这叫做阿尔法-贝塔修剪。**
阿尔法贝塔剪枝
阿尔法贝塔剪枝是一种极大极小算法的优化技术。这将删除一些不应该扩展的节点,因为已经找到了更好的移动。当应用于极大极小算法时,它将返回与极大极小算法相同的动作,但速度更快。它被称为阿尔法贝塔修剪,因为它需要两个新的参数称为阿尔法和贝塔。
Alpha:最大化者在当前状态或最大化者回合之前可以保证的最佳值
Beta:最小化者在当前状态或最小化者回合之前可以保证的最佳值
总的原则是:给定一个状态 s 在树的某处。如果玩家可以选择在找到 s 之前做出动作 m ,那么 s 将永远不会到达。
让我们以图片 1 为例:
[Image 3] AB Pruning Source: AI: A Modern Approach Book
记住搜索算法使用的是 DFS(深度优先搜索)!
让我们一步一步来:
- 将 Alpha 初始化为-inf,将 Beta 初始化为 inf。如果阿尔法在最大化回合,它将被更新;如果它在最小化回合,它将被更新。它们将被传递给状态/节点的子节点。
- 展开一个(初始状态)可能的动作。它会产生 B,C,D 状态。从第一个索引(B)开始
- 轮到迷你 B 了。展开 B 可能的行动,进入孩子并检查其分数。尽可能少地更新测试版。我们会发现 Beta = 3 = min(3,12,8,inf)。α仍然是-inf。
- 假设深度为 2,将最小分数(也就是 3)返回给 A,现在回到 Maximizer turn A ,更新 Alpha = 3 = max(-inf,3)。然后我们转到 C 状态/节点,第二个子节点:
- 现在转到状态 C,最小化回合 C 带有传递的阿尔法参数 3。在 C 中,我们发现 C 找到的第一个孩子的分数是 2。更新β= 2 = min(INF,2)。现在,让我们想想。最大化器会选择这个动作吗?最大化者已经找到了更好的移动,得分为 3。当然不是,如果是最优代理的话。所以,不要在 c 中检查另一个可能的动作,这是修剪过程。返回最好的分数(是 2)。如果β≤α那么就把它修剪掉。
- Maximizer Turn A :计算 max(alpha,2) = alpha。检查最后一个孩子 c。
- 极小化回合 C :计算 min(inf,14),将 beta 更新为 14,检查 beta 是否≤ alpha (14 ≤ 3)且未修剪。计算下一个孩子。β≤α(5≤3)且不修剪。最后,检查β≤α(2≤3)是否应该修剪。因为在这个节点之后没有任何子节点。没有可以修剪的节点。如果首先找到返回 2 的子节点,就可以删除它。在这之后,返回 2 到 a。
- **Maximizer 转 A,计算 max(alpha,2) = alpha。**然后就搞定了。返回 3 作为在树中找到的最佳分数。
将其实现到evopowness(临时名称)中。
这是用于 Minimax Alpha Beta 修剪的类:
(注:self.node_expanded
用于调试目的)
我们添加 alpha 和 beta 作为参数,默认为-inf
和inf
。然后我们添加这些行:
用于最大化器
alpha = max(alpha, best_value)
if beta <= alpha:
break
和
针对最小化器
beta = min(beta, best_value)
if beta <= alpha:
break
就这些,我们来测试一下😃!
测试代理
我们将玩深度= 4 的MinimaxABAgent
转到 controller/game_controller.py 中的第 52 行,将其改为
self.ai_agent = MinimaxABAgent(max_depth=4, player_color=0)
[Image 4] : I lost to the AI 😢
其行为与 minimax 相同,但速度更快。让我们看看运行时间,直到 24 转。
我们可以看到,在不使用剪枝技术的情况下,花费的时间更快。直到最后,我发现搜索最佳动作的最差运行时间是 17s,有 2218 个扩展节点(66 转)。它的速度更快,因为修剪删除不必要的节点。
继续挑战人工智能。人工智能非常具有挑战性。有时它会在开始时瞄准国王。我输给人工智能一次是因为对国王的突然袭击,我没有准备好保护国王😢。
尽管如此,它是可以赢的。人工智能不为国王辩护。我认为它需要更多的深度来了解它。它将花费更长的运行时间。嗯,没有人会想测试他/她的耐心,玩一个需要太长时间的人工智能(你想等一分钟或更长时间才能轮到它?).
[Image 5] Source : https://pixabay.com/en/robot-future-modern-technology-1658023/
Minimax 也不考虑除了在所选深度中发现的状态之外的状态。极大极小只关心在其中找到的状态。不可能从开始就搜索所有节点到终端状态,因为这会导致内存错误。计算量大(O(action^depth))这是极小极大的弱点,即使使用剪枝技术进行了改进。
我们需要一些考虑状态而不是选择深度的技术,也可以让 AI 更快地选择动作。我们将在下一部分看到它😄。
结论
我们已经创建了 Minimax 算法使用的效用和评估函数。虽然性能不错,但是极大极小算法就是这么慢。为了弥补这个缺陷,我们对算法进行了修剪。这叫做阿尔法贝塔剪枝。动作的模式是相同的,并且不使用修剪会更快。即便如此,极大极小阿尔法贝塔剪枝也有它的缺陷。它只能考虑和观察 n 向前移动,即 n 需要变小。如果 n 很大,那么选择一个动作在计算上将是昂贵的。我们需要一些算法,考虑到进一步的转折,需要更快。
编后记
感谢您阅读我关于人工智能的第三篇文章。我需要一些建设性的反馈,以提高我的写作水平和人工智能知识。请手下留情😆!
我只是想把我的知识分享给读者。分享知识很好,因为它可以帮助有需要的人。我正在学习这个领域,想分享我所学到的东西。如果有人告诉我这篇文章有什么问题,那就太好了。
很抱歉在图形用户界面上的糟糕用户体验(UX)😞。它在代码中也有混乱和不够的文档。我希望我有时间修理它。也许有人能帮我修好它?
下一部分(第 3 部分)将进入强化学习。我们将逐一探讨各种强化学习。在此之前,我将创建一篇关于我如何设计 GUI 的文章,并展示这个项目中的类的结构。这将是 1.5 部分。希望我能在这个星期天发表文章。
我们将在强化学习中进行实验。恐怕要花很多时间来试验它。我想,两周是我能发表文章的最小间隔。有人想和我一起做实验吗?给我精神去写下一部分✌️.
如果你想从我这里得到另一篇这样的文章,请拍下这篇文章👏 👏。这将鼓舞我写下一篇文章。我保证会写出更好的文章。
下一篇文章再见!
“flat lay photography of coffee latte in teacup on table” by Hanny Naibaho on Unsplash
系列文章
第 1 部分:从头开始为你自己的棋盘游戏创造人工智能——准备——第 1 部分
第 2 部分:从零开始为你自己的棋盘游戏创造 AI——Minimax——第 2 部分
第 3 部分:从头开始为你自己的棋盘游戏创造 AI——alpha zero——第 3 部分
来源
拉塞尔,斯图尔特 j,和彼得诺维格。人工智能:现代方法。普伦蒂斯霍尔出版社,2010 年。
[## 博弈论中的极大极小算法|第一集(简介)- GeeksforGeeks
极客的计算机科学门户。它包含写得很好,很好的思想和很好的解释计算机科学和…
www.geeksforgeeks.org](https://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-1-introduction/)
用 Python 和 Azure DSVM 创建 Azure 机器学习 Web 服务
上周,当我在阅读关于微软 CNTK 的文档,并从他们的 GitHub 库中检查代码样本时,我碰到了另一个 GitHub 库,它是关于 Azure 机器学习操作化。
Azure Machine Learning operational ization 是 Azure CLI 的一个组件,它支持您通过 Vienna 使用 CNTK、SPARK 和 Python 机器学习平台创建的模型的可操作性。
我最近一直在玩 Python 和机器学习,所以我想我应该试试这个,并使用用 Python 构建的机器学习模型创建一个 AzureML Web 服务。
重要提示:撰写本文时,Azure 机器学习操作化仍处于预览阶段。未来可能会发生一些突破性的变化。
入门指南
要开始 Azure 机器学习操作化,您需要以下内容:
- Azure 账户(如果你没有,你可以在这里获得免费试用
- azure Data Science Virtual Machine(DSVM)——虽然这并不是必须的,但我认为这是最快最简单的入门方式。你可以点击查看文档来创建一个。
更新(实际上是重新安装)Azure CLI 包
一旦你创建了 Azure DSVM,我们要做的第一件事就是通过卸载和安装来更新我们的azure-cli
、azure-cli-ml
和azure-ml-api-sdk
包。
$ pip uninstall azure-cli azure-cli-ml azure-ml-api-sdk
$ pip install azure-cli azure-cli-ml azure-ml-api-sdk
以前,我尝试用pip install
中的--upgrade
选项更新软件包,但我一直在升级软件包时出错。因此,卸载并安装它可以解决问题,因为稍后,我们将使用这些包来生成一个模式,并将我们训练好的模型部署到我们的 web 服务中。
构建性别分类器模型
接下来,我们将使用scikit-learn
机器学习库创建一个模型,该模型将根据给定的身体指标(身高、宽度和鞋码)来预测这个人是男是女。创建一个名为train.py
的文件,您可以复制下面的代码:
运行代码,它会在你的项目文件夹中创建一个model.pkl
。
创建 score.py
要将我们的模型部署为 web 服务,我们需要创建一个用于生成模式 JSON 文件的score.py
。
运行score.py
,您应该会看到service_schema.json
已经创建。现在,我们的项目文件夹应该包含以下文件:
PROJECT_FOLDER
- train.py
- score.py
- model.pkl
- service_schema.json
使用 Azure CLI 创建和部署 Web 服务
现在我们已经设置好了一切,我们现在将通过执行以下操作来创建我们的 web 服务并部署模型:
在 Azure CLI 中验证您的帐户
为了能够使用azure cli
创建我们的环境和模型管理帐户,我们首先需要使用以下命令进行认证:
$ az login
这将显示一条信息,提示您需要打开网络浏览器,进入https://aka.ms/devicelogin并输入终端中提供的代码。
创造环境
一旦您能够使用azure cli
验证您的帐户,键入以下命令来创建我们的环境:
$ az ml env setup -c -n YOUR_CLUSTER_NAME -l YOUR_LOCATION
这是我在本教程中使用的:
$ az ml env setup -c -n azmldemo -l australiaeast
记住现在支持的位置是westcentralus
、eastus2
和australiaeast
也很重要。
创建模型管理帐户
我们的环境至少需要 10-20 分钟才能完成配置,因此在等待的同时,我们现在可以创建我们的模型管理帐户,这是一个一次性设置的事情。
$ az ml account modelmanagement create -l YOUR_LOCATION -n YOUR_MODEL_MANAGEMENT_ACCOUNT_NAME -g YOUR_RESOURCE_GROUP
这是我在本教程中使用的:
$ az ml account modelmanagement create -l australiaeast -n azmldemoacc -g azmldemorg
如果您想知道,azmldemorg
资源组是在我们之前创建环境时自动创建的,它基本上是在我们的集群名称azmldemo
上添加了一个 rg 。
设置模型管理帐户和环境
一旦成功创建了模型管理帐户和您的环境,我们现在将使用以下命令设置模型管理帐户和环境:
$ az ml account modelmanagement set -n YOUR_MODEL_MANAGEMENT_ACCOUNT_NAME -g YOUR_RESOURCE_GROUP$ az ml env set -n YOUR_CLUSTER_NAME -g YOUR_RESOURCE_GROUP
这是我在本教程中使用的:
$ az ml account modelmanagement set -n azmldemoacc -g azmldemorg
$ az ml env set -n azmldemo -g azmldemorg
同样重要的是要记住,您的环境供应状态应该显示成功,以便您能够成功设置它。您可以使用以下命令检查资源调配状态:
$ az ml env show -g YOUR_RESOURCE_GROUP -n YOUR_CLUSTER_NAME
创建 Web 服务
一旦您能够成功设置您的模型管理帐户和环境,我们现在将通过以下方式创建 web 服务:
- 转到我们已经创建了性别分类器模型和 JSON 模式的项目文件夹
- 键入以下命令创建 web 服务:
$ az ml service create realtime -f score.py --m model.pkl -s service_schema.json -n genderclassifier -r pythonor you can also use --help or -h to know the other arguments available$ az ml service create realtime -h
测试 Web 服务
一旦成功地创建了 web 服务,您就可以使用下面的命令来获取可以用来测试 web 服务的所有重要细节(像示例 CLI 命令、Swagger URL、授权载体密钥等等):
$ az ml service usage realtime -i YOUR_SERVICE_ID
您可以使用以下命令获得您的服务 Id :
$ az ml service list realtime
运行示例 CLI 命令
$ az ml service run realtime -i YOUR_SERVICE_ID -d "{\"input_df\": [{\"width\": 60, \"shoe_size\": 38, \"height\": 190}]}"
当您在终端中运行时,应该是这样的:
通过邮递员测试
这里有一个关于如何通过Scoring URL
测试 web 服务的例子
我们已经完成了,那么你对 Azure 机器学习的可操作性有什么看法?同样,您可以在这里查看他们的 GitHub 库中的其他示例。
创建具有业务影响的更好的数据科学项目:使用 R
找份工作并不容易,你需要让自己与众不同。
仅仅展示你知道如何使用像 scikit-learn 或 ggplot2 这样的工具是不够的,这是已知的。
所以问题是,你能做什么?
我最喜欢的策略之一是展示商业影响。
我所说的**“业务影响”是什么意思**
在一天结束时,你将直接或间接地从事与业务相关的工作。
那可能是降低成本、增加收入、改善客户体验等等。
在这篇文章中,我将一步一步地向你展示如何建立一个客户流失预测模型来显示重大的商业影响。
这里有一个简单的概述:
- 工作经历
- 逻辑回归
- 准备数据
- 拟合模型
- 做预测
- 业务影响
工作经历
在任何现实世界的数据科学项目开始时,你都需要问一系列问题。
这里有几个好的开始:
- 你想解决什么问题?
- 你的潜在解决方案是什么?
- 你将如何评价你的模型?
继续前面的客户流失想法,让我们假设你在电信行业工作。
有一天,你的老板找到你,问你,“我们如何利用现有的数据来改善业务?”
让我们来看一下如何详细回答每个问题,以提出解决老板问题的战略方法。
你想解决什么问题?
在查看了现有数据后,您发现获得新客户的成本大约是留住现有客户的五倍。
现在,更集中的问题变成了,“我们如何提高客户保持率以降低成本?”
你的潜在解决方案是什么?
由于我们可以访问现有的客户数据,我们可以尝试建立一个机器学习模型来预测客户流失。
为了避免过于抽象而无法向管理层解释的事情变得过于复杂,我们将使用逻辑回归模型。
你将如何评价你的模型?
我们将使用一系列机器学习评估指标 ( ROC ,AUC,灵敏度,特异性)以及业务导向指标(成本节约)。
逻辑回归
既然我们已经熟悉了业务背景,并且已经确定了问题的范围,那么让我们来看看我们潜在的解决方案。
我们可以尝试使用许多机器学习模型。都有利弊。
为了使事情简单,让我们只坚持逻辑回归。
逻辑回归是一个线性分类器。由于我们试图预测“流失”或“没有流失”,分类模型正是我们所需要的。
这是一个很棒的模型,因为它比许多其他模型更容易解释,比如随机森林。我所说的“解释”是指我们可以更容易地看到特征和输出之间的关系。
逻辑回归的缺点是它有一个偏向线性拟合的偏差。如果决策边界不是线性的,它的性能可能不如随机森林这样的模型。
基本上,我们是在用灵活性来换取可解释性。在实现机器学习模型时,这始终是一个需要考虑的问题。
准备数据
下一步是准备数据。
此工作流程因项目而异,但对于我们的示例,我将使用以下工作流程:
- 导入数据
- 快速看一眼
- 清理数据
- 拆分数据
我在工作流程中省略了一个完整的探索阶段。在以后的文章中,我会详细介绍这一点,因为我认为这与建模阶段同等重要,甚至更重要。
1)导入数据
让我们首先导入数据。你可以到我们的 github 页面去拿一份。
我们将利用 Tidyverse 库。这是使用 r 的数据科学家的必备库,请务必查看他们的文档。
library(tidyverse)
# setting the working directory
path_loc <- "C:/Users/Jonathan/Desktop/post"
setwd(path_loc)
# reading in the data
df <- read_csv("Telco Data.csv")
我喜欢在开始时设置工作目录的路径。确保将您的path_loc
变量修改到您的代码和数据集所在的工作目录。
由于这是一个 csv 文件,我使用了read_csv
函数将数据读入数据帧df
。
2)快速浏览一下
导入数据后,您可能想快速浏览一下。我认为“快速浏览”就是这样,而不是探索数据。
我喜欢从查看特性列的维度和名称开始。
# dimensions of the data
dim_desc(df)
# names of the data
names(df)> # dimensions of the data
> dim_desc(df)
[1] "[7,043 x 21]"
>
> # names of the data
> names(df)
[1] "customerID" "gender" "SeniorCitizen" "Partner" "Dependents" "tenure"
[7] "PhoneService" "MultipleLines" "InternetService" "OnlineSecurity" "OnlineBackup" "DeviceProtection"
[13] "TechSupport" "StreamingTV" "StreamingMovies" "Contract" "PaperlessBilling" "PaymentMethod"
我们可以看到有 21 个特征和 7,043 行。有各种各样的功能,如TotalCharges
和tenure
。我们将要尝试和预测的输出是“流失”。
另一个很棒的功能是glimpse
。这使我们能够更详细地快速查看我们的功能。
# taking a look at the data
glimpse(df)> glimpse(df)
Observations: 7,043
Variables: 21
$ customerID "7590-VHVEG", "5575-GNVDE", "3668-QPYBK", "7795-CFOCW", "9237-HQITU", "9305-CDSKC", "1452-K...
$ gender "Female", "Male", "Male", "Male", "Female", "Female", "Male", "Female", "Female", "Male", "...
$ SeniorCitizen 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1...
$ Partner "Yes", "No", "No", "No", "No", "No", "No", "No", "Yes", "No", "Yes", "No", "Yes", "No", "No...
$ Dependents "No", "No", "No", "No", "No", "No", "Yes", "No", "No", "Yes", "Yes", "No", "No", "No", "No"...
$ tenure 1, 34, 2, 45, 2, 8, 22, 10, 28, 62, 13, 16, 58, 49, 25, 69, 52, 71, 10, 21, 1, 12, 1, 58, 4...
$ PhoneService "No", "Yes", "Yes", "No", "Yes", "Yes", "Yes", "No", "Yes", "Yes", "Yes", "Yes", "Yes", "Ye...
$ MultipleLines "No phone service", "No", "No", "No phone service", "No", "Yes", "Yes", "No phone service",...
$ InternetService "DSL", "DSL", "DSL", "DSL", "Fiber optic", "Fiber optic", "Fiber optic", "DSL", "Fiber opti...
$ OnlineSecurity "No", "Yes", "Yes", "Yes", "No", "No", "No", "Yes", "No", "Yes", "Yes", "No internet servic...
$ OnlineBackup "Yes", "No", "Yes", "No", "No", "No", "Yes", "No", "No", "Yes", "No", "No internet service"...
$ DeviceProtection "No", "Yes", "No", "Yes", "No", "Yes", "No", "No", "Yes", "No", "No", "No internet service"...
$ TechSupport "No", "No", "No", "Yes", "No", "No", "No", "No", "Yes", "No", "No", "No internet service", ...
$ StreamingTV "No", "No", "No", "No", "No", "Yes", "Yes", "No", "Yes", "No", "No", "No internet service",...
$ StreamingMovies "No", "No", "No", "No", "No", "Yes", "No", "No", "Yes", "No", "No", "No internet service", ...
$ Contract "Month-to-month", "One year", "Month-to-month", "One year", "Month-to-month", "Month-to-mon...
$ PaperlessBilling "Yes", "No", "Yes", "No", "Yes", "Yes", "Yes", "No", "Yes", "No", "Yes", "No", "No", "Yes",...
$ PaymentMethod "Electronic check", "Mailed check", "Mailed check", "Bank transfer (automatic)", "Electroni...
$ MonthlyCharges 29.85, 56.95, 53.85, 42.30, 70.70, 99.65, 89.10, 29.75, 104.80, 56.15, 49.95, 18.95, 100.35...
$ TotalCharges 29.85, 1889.50, 108.15, 1840.75, 151.65, 820.50, 1949.40, 301.90, 3046.05, 3487.95, 587.45,...
$ Churn "No", "No", "Yes", "No", "Yes", "Yes", "No", "No", "Yes", "No", "No", "No", "No", "Yes", "N...
3)清理数据
在开始使用逻辑回归模型之前,我们需要做一些小的清理。
让我们从将字符变量突变为因子开始。
# changing character variables to factors
df <- df %>% mutate_if(is.character, as.factor)
在这段代码中,我们使用 dplyr 的mutate_if
函数将字符变量转换为因子类型。
%>%
被称为管道操作员。它来自马格里特图书馆,也是 Tidyverse 的一部分。
这个操作符的基本思想是使你的代码更具可读性。你可以在这里了解更多关于管道运营商的信息。
让我们也将SeniorCitizen
变量从一个整数改为一个因子。
# changing SeniorCitizen variable to factor
df$SeniorCitizen <- as.factor(df$SeniorCitizen)
接下来,我们将检查丢失的值。我们可以使用来自 purrr 库中的map
函数来完成这项工作。如果你还没有猜到,这个库也是 Tidyverse 的一部分。
# looking for missing values
df %>% map(~ sum(is.na(.)))> df %>% map(~ sum(is.na(.)))
$`customerID`
[1] 0
$gender
[1] 0
$SeniorCitizen
[1] 0
$Partner
[1] 0
$Dependents
[1] 0
$tenure
[1] 0
$PhoneService
[1] 0
$MultipleLines
[1] 0
$InternetService
[1] 0
$OnlineSecurity
[1] 0
$OnlineBackup
[1] 0
$DeviceProtection
[1] 0
$TechSupport
[1] 0
$StreamingTV
[1] 0
$StreamingMovies
[1] 0
$Contract
[1] 0
$PaperlessBilling
[1] 0
$PaymentMethod
[1] 0
$MonthlyCharges
[1] 0
$TotalCharges
[1] 11
$Churn
[1] 0
我们可以看到TotalCharges
有 11 个缺失值。为了替换这些丢失的值,我们只需要用中间值做一个简单的替换。
# imputing with the median
df <- df %>%
mutate(TotalCharges = replace(TotalCharges,
is.na(TotalCharges),
median(TotalCharges, na.rm = T)))
对于替换缺失值的更严格的技术方法,请查看来源。
我们要做的最后一件事是移除CustomerID
特性。这是每个客户的唯一标识符,所以它可能不会给我们的模型添加任何有用的信息。
# removing customerID; doesn't add any value to the model
df <- df %>% select(-customerID)
如果你想测试这个,你可以一直保持这个特性,看看结果如何比较。
4)拆分数据
为了确保我们没有过度拟合我们的模型,我们将把数据分成训练集和测试集。这被称为交叉验证。
我们将在训练集上训练模型,然后在测试集上测试它的性能。我们将随机选择 75%的数据作为我们的训练集,25%作为我们的测试集。我鼓励你尝试不同的分割,看看你的结果如何比较(也许你的训练/测试集是 80%/20%和 60%/40%)。
为了创建分割,我们将使用插入符号包。让我们从导入 Caret 并设置种子开始,这样我们的结果是可重复的。
library(caret)
# selecting random seed to reproduce results
set.seed(5)
接下来,我们将使用createDataPartition
函数选择 75%的数据用于训练集。这将随机选择 75%的行。
# sampling 75% of the rows
inTrain <- createDataPartition(y = df$Churn, p=0.75, list=FALSE)
最后,我们将使用上面的行样本创建我们的训练和测试数据帧。
# train/test split; 75%/25%
train <- df[inTrain,]
test <- df[-inTrain,]
还有其他方法,如 k 倍交叉验证,所以一定要仔细阅读这些方法。
有关如何在 Caret 中实现 k-fold 交叉验证的信息,请在 R 控制台中键入help(“createFolds”)
。
拟合模型
既然我们已经将数据分成了训练集和测试集,现在是时候拟合模型了。
为了实现逻辑回归模型,我们将使用广义线性模型 (GLM)函数glm
。
有不同类型的 GLMs ,其中包括逻辑回归。为了指定我们想要执行二元逻辑回归,我们将使用参数family=binomial
。
下面是拟合逻辑回归模型的完整代码:
# fitting the model
fit <- glm(Churn~., data=train, family=binomial)
在下一节中,我们将进行预测并评估我们的模型。
做预测
现在我们已经拟合了我们的模型,是时候看看它的表现了。
为此,我们将使用test
数据集进行预测。我们将传递上一节中的fit
模型。为了预测概率,我们将指定type=”response”
。
# making predictions
churn.probs <- predict(fit, test, type="response")
head(churn.probs)> head(churn.probs)
1 2 3 4 5 6
0.32756804 0.77302887 0.56592677 0.20112771 0.05152568 0.15085976
由于我们预测的Churn
变量是“是”或“否”,所以我们希望将这些概率转换成二元响应。
我不确定 R 是如何编码响应的,但是我可以使用contrasts
函数快速检查这一点。
# Looking at the response encoding
contrasts(df$Churn)> contrasts(df$Churn)
Yes
No 0
Yes 1
查看结果,我们可以看到Yes
正在使用1
进行编码。
现在我们知道了响应编码,我们可以将预测结果转换成Yes
和No
响应。
我们将响应阈值设置为0.5
,因此如果预测概率高于0.5
,我们将把这个响应转换为Yes
。
最后一步是将角色反应转换成因素类型。这是为了使逻辑回归模型的编码是正确的。
# converting probabilities to "Yes" and "No"
glm.pred = rep("No", length(churn.probs))
glm.pred[churn.probs > 0.5] = "Yes"
glm.pred <- as.factor(glm.pred)
稍后我们将进一步了解阈值,所以不要担心我们为什么将它设置为0.5
。现在,我们将在这个例子中使用这个值。
进行预测的一个重要部分是评估和验证模型。
让我们详细看看一些评估指标,并以一种更严格的模型验证方法结束, k 倍交叉验证。
评估模型
在我们做出预测之后,是时候评估我们的模型了。
快速做到这一点的一个很好的工具是使用 Caret 中的confusionMatrix
函数。
我们将输入我们的glm.pred
预测数组,以及来自test$Churn
的实际结果。
最后,我们将使用positive=”Yes”
将正类指定为“Yes”。
# creating a confusion matrix
confusionMatrix(glm.pred, test$Churn, positive = "Yes")> confusionMatrix(glm.pred, test$Churn, positive = "Yes")
Confusion Matrix and Statistics
Reference
Prediction No Yes
No 1165 205
Yes 128 262
Accuracy : 0.8108
95% CI : (0.7917, 0.8288)
No Information Rate : 0.7347
P-Value [Acc > NIR] : 4.239e-14
Kappa : 0.4877
Mcnemar's Test P-Value : 3.117e-05
Sensitivity : 0.5610
Specificity : 0.9010
Pos Pred Value : 0.6718
Neg Pred Value : 0.8504
Prevalence : 0.2653
Detection Rate : 0.1489
Detection Prevalence : 0.2216
Balanced Accuracy : 0.7310
'Positive' Class : Yes
这个函数产生一个混淆矩阵,以及其他感兴趣的统计数据。
混淆矩阵显示了每个类别中有多少正确和不正确的预测。
下面快速浏览一下我们模型中的混淆矩阵:
我们可以看到,该模型正确预测了 1165 次“否”,而在实际回答为“是”的情况下,错误预测了 205 次“否”。
同样,当实际答案是“是”的时候,该模型正确地预测了“是”262 次。与此同时,它错误地预测了“是”,而实际的回答是“否”128 次。
我们的总体准确率是 81%。一个简单的基线模型是预测多数类,在我们的例子中是“否”。
如果我们只是预测多数类,我们的准确率将是 73%。测试集中有 1760 个观察值,1293 个是“否”。如果你用 1293 除以 1760,你就可以得到 73%的准确率。
其他一些有用的指标是 灵敏度和特异性 。由于我们的类别略有不平衡(约 73%=“否”,约 27%=“是”),这些指标可能更有用。
灵敏度表示**“真阳性”**率。这是对我们预测正类的准确程度的度量,在我们的例子中是“是”。
confusionMatrix
函数直接报告这个,但是如果你想自己计算,用“真阳性”除以“真阳性”和“假阴性”之和。这里有一个形象化的例子来说明这一点:
另一个有用的指标是特异性,它表示**“真阴性”**率。这是对我们预测负类的准确度的一种衡量。这里有一个解释特异性的图像:
另一个有用的指标是受试者工作特征曲线下的面积,也称为 AUC。
ROC 最初在第二次世界大战期间用于分析雷达信号,它是一张真阳性率与假阳性率的曲线图。
回到我们最初的模型,我声明我将使用 0.5 作为做出“是”(或积极)预测的阈值。不过,我并没有很好的理由选择 0.5。
ROC 是一个很好的工具,因为它绘制了阈值变化时 TPR 与 FPR 的关系。我们可以使用 ROCR 库绘制 ROC 曲线。以下是完整的 R 代码:
library(ROCR)
# need to create prediction object from ROCR
pr <- prediction(churn.probs, test$Churn)
# plotting ROC curve
prf <- performance(pr, measure = "tpr", x.measure = "fpr")
plot(prf)
如前所述,另一个有用的指标是 ROC 曲线下的面积,称为 AUC 。
AUC 可以取 0 到 1 之间的任何值,1 为最佳值。这是一种将 ROC 归结为一个数字来评估模型的简便方法。下面是评估我们模型的 R 代码:
# AUC value
auc <- performance(pr, measure = "auc")
auc <- auc@y.values[[1]]
auc> auc
[1] 0.8481338
我们的模型的 AUC 为 0.85,相当不错。如果我们只是随机猜测,我们的 ROC 将是一条 45 度线。这相当于 AUC 为 0.5。
我们胜过随机猜测,所以我们知道我们的模型至少增加了一些价值!
k 倍交叉验证
既然我们已经训练、测试和评估了我们的模型,让我们更严格地评估模型。
我们将数据集分为训练数据集和测试数据集,这是一种防止过度拟合的好方法。
一个更好的方法是使用 K 倍交叉验证。
在这种模型验证技术中,我们通过指定一定数量的“折叠”将数据随机划分为测试集和训练集。在上面的例子中,折叠的数量是 k=4。
在我们对每个折叠运行模型后,我们对每个折叠的评估指标进行平均。因此,如果我们使用 ROC 运行模型四次,我们将对四个 ROC 值进行平均。这是尝试和防止过度拟合模型的好方法。
常用的折叠数是 10,所以我们将在数据中使用 10。我们还将重复这个过程 3 次,只是为了给我们的方法增加一点技术上的严谨性。以下是完整的 R 代码:
# setting a seed for reproduceability
set.seed(10)
# changing the positive class to "Yes"
df$Churn <- as.character(df$Churn)
df$Churn[df$Churn == "No"] <- "Y"
df$Churn[df$Churn == "Yes"] <- "N"
df$Churn[df$Churn == "Y"] <- "Yes"
df$Churn[df$Churn == "N"] <- "No"
# train control
fitControl <- trainControl(## 10-fold CV
method = "repeatedcv",
number = 10,
## repeated 3 times
repeats = 3,
classProbs = TRUE,
summaryFunction = twoClassSummary)
# logistic regression model
logreg <- train(Churn ~., df,
method = "glm",
family = "binomial",
trControl = fitControl,
metric = "ROC")
logreg> logreg
Generalized Linear Model
7043 samples
19 predictor
2 classes: 'No', 'Yes'
No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times)
Summary of sample sizes: 6338, 6339, 6338, 6339, 6339, 6339, ...
Resampling results:
ROC Sens Spec
0.8455546 0.5500297 0.89602
我们从使用trainControl
功能设置 k 倍 CV 开始。所有的输入都非常简单。正如我之前提到的,我们使用 10 次折叠,重复 3 次。
接下来我们训练我们的模型。正如我们前面所做的,我们使用“glm”方法中的“二项式”族。为了评估我们的模型,我们将使用“ROC”。该模型实际上是报告 AUC,但我们在train
函数中指定的方式是用metric=”ROC”
。
您还会注意到,我在trainControl
函数的代码前将正类更改为“Yes”。我这样做是为了将敏感性和特异性与我们以前的结果进行比较。这只是函数中的一个小问题,所以不要纠结于此。
结果与我们以前得到的相似。
和之前一样,我们的 AUC 是 0.85 。这在输出中报告为 ROC,但实际上是 AUC。
真阳性率(敏感性)为 0.55 ,而真阴性率(特异性)为 0.90 。
业务影响
到目前为止,我们已经使用了 k 倍交叉验证和逻辑回归来预测客户流失。
我们已经查看了有用的评估指标,如 AUC 、灵敏度和特异性。
这很好,但现在怎么办?
如果我们去见首席执行官并单独展示这些结果,他或她会说**“那又怎么样?”**
我们开发该模型的实际目标是展示业务影响。在我们的案例中,这将是成本节约。
让我一步一步地介绍一下我们如何节约成本。
首先,让我们对各种成本做一些假设。
我们假设电信行业的客户获取成本约为300 美元。如果我们预测某个客户不会流失,但实际上他们会流失(假阴性,FN),那么我们就必须出去花 300 美元为该客户找一个替代品。
现在让我们假设获得一个新客户比留住一个现有客户要贵 5 倍。如果我们预测某个客户会流失,我们需要花费60 美元来留住那个客户。
有时我们会正确地预测客户会流失(真阳性,TP),有时我们会错误地预测客户会流失(假阳性,FP)。在这两种情况下,我们将花费 60 美元来留住客户。
最后,在这种情况下,我们正确地预测客户不会流失(真阴性,TN)。在这种情况下,我们不会花钱。这些是快乐的顾客,我们正确地将他们定义为快乐的顾客。
这里是成本的快速汇总:
- FN (预测客户不会流失,但他们确实流失了): $300
- TP (预测客户会流失,而实际上他们会流失): $60
- FP (预测客户会流失,而实际上他们不会): $60
- TN (预测客户不会流失,而实际上他们不会): $0
如果我们将每种预测类型(FN、TP、FP 和 TN)的数量乘以与每种类型相关联的成本,并将它们相加,那么我们可以计算出与我们的模型相关联的总成本。这个等式是这样的:
成本= FN($ 300)+TP($ 60)+FP($ 60)+TN($ 0)
作为一个例子,让我们假设我们对每一个做出如下数量的预测:
- FN = 10
- TP = 5
- FP = 5
- TN = 5
那么我们的总成本将是 3600 美元。
10 *(300 美元)+5 *(60 美元)+5 *(60 美元)+5 *(0 美元)= $3600
现在让我们将这个成本评估应用到我们的模型中。
我们将从拟合模型开始,并以概率的形式进行预测。
# fitting the logistic regression model
fit <- glm(Churn~., data=train, family=binomial)
# making predictions
churn.probs <- predict(fit, test, type="response")
接下来,让我们创建一个阈值向量和一个成本向量。
# threshold vector
thresh <- seq(0.1,1.0, length = 10)
#cost vector
cost = rep(0,length(thresh))
阈值向量将保存每个模型的阈值。之前,我们只是使用 0.5 作为阈值,但让我们看看阈值在 0 和 1 之间的增量为 0.1(例如,0.1、0.2、0.3…0.9、1.0)。
成本向量将是长度为 10 的向量,以零开始。我们将在循环函数时填充它,并在循环过程中评估每个阈值。为了评估成本,我们将使用刚刚讨论过的相同成本等式。
现在,我们将创建一个 for 循环,使用各种阈值进行预测,并评估每个阈值的成本。
# cost as a function of threshold
for (i in 1:length(thresh)){
glm.pred = rep("No", length(churn.probs))
glm.pred[churn.probs > thresh[i]] = "Yes"
glm.pred <- as.factor(glm.pred)
x <- confusionMatrix(glm.pred, test$Churn, positive = "Yes")
TN <- x$table[1]/1760
FP <- x$table[2]/1760
FN <- x$table[3]/1760
TP <- x$table[4]/1760
cost[i] = FN*300 + TP*60 + FP*60 + TN*0
}
我没有使用 TN、FP、FN 和 TP 的每个结果的总数,而是使用了一个百分比。这就是为什么我用混淆矩阵取出每个值,然后除以 1760。
在我们的测试集中有 1760 个观察值,这就是这个数字的来源。通过这样做,我正在计算每位顾客的成本。
现在让我们假设我们的公司正在使用我们称之为**“简单模型”,它只是默认为 0.5 的阈值。我们可以继续下去,拟合模型,进行预测,并计算成本。我们将这个模型称为cost_simple
。**
# simple model - assume threshold is 0.5
glm.pred = rep("No", length(churn.probs))
glm.pred[churn.probs > 0.5] = "Yes"
glm.pred <- as.factor(glm.pred)
x <- confusionMatrix(glm.pred, test$Churn, positive = "Yes")
TN <- x$table[1]/1760
FP <- x$table[2]/1760
FN <- x$table[3]/1760
TP <- x$table[4]/1760
cost_simple = FN*300 + TP*60 + FP*60 + TN*0
最后,我们可以把所有的结果放在一个数据框中,并把它们画出来。
# putting results in a dataframe for plotting
dat <- data.frame(
model = c(rep("optimized",10),"simple"),
cost_per_customer = c(cost,cost_simple),
threshold = c(thresh,0.5)
)
# plotting
ggplot(dat, aes(x = threshold, y = cost_per_customer, group = model, colour = model)) +
geom_line() +
geom_point()
查看结果,我们可以看到,在阈值为 0.2 时,每位客户的最低成本约为40.00 美元**。**
我公司目前正在实施的**【简单】模式每个客户的成本约为48.00 美元,门槛为 0.50 。**
如果我们假设我们有大约 500,000 的客户群,从简单模型到优化模型的转换产生了 400 万美元的成本节约。****
# cost savings of optimized model (threshold = 0.2) compared to baseline model (threshold = 0.5)
savings_per_customer = cost_simple - min(cost)
total_savings = 500000*savings_per_customer
total_savings> total_savings
[1] 4107955
根据企业的规模,这种类型的成本节约可能会产生重大的业务影响。
结论
在这篇文章中,我们开发了一个机器学习模型来预测客户流失。
具体来说,我们完成了以下每个步骤:
- 工作经历
- 逻辑回归
- 准备数据
- 拟合模型
- 做预测
- 业务影响
最后,我们针对客户流失问题开发了一个优化的逻辑回归模型**。**
假设公司使用的逻辑回归模型的默认阈值为 0.5 ,我们能够确定最优阈值实际上是 0.2 。
这将每位客户的成本从48.00 美元降至40.00 美元**。由于客户群约为 50 万,这将产生每年 400 万美元的**成本节约。这是一个重大的业务影响!****
尽管这是一个纯粹假设的例子,但它与您在现实世界中遇到的情况非常相似。
能够有效地识别问题并展示真实的业务影响将使你在就业市场上从其他数据科学家中脱颖而出。
能够实现机器学习模型并对其进行评估是一回事。如果你能做到所有这些,并像我们在这篇文章中所做的那样,以商业影响的形式展示结果,那么你就能顺利找到工作了!
从头开始为你自己的棋盘游戏创建人工智能——准备——第 1 部分
基于我的一个小项目,我想出了一个棋盘游戏,EvoPawness(临时名称)
Photo by Trent Jackson on Unsplash
大家好!这是我在 medium 发表的第二篇关于人工智能(AI)的文章。我想和大家分享我在空闲时间做的小项目的进展。这是我想出的一个棋盘游戏。**evopheness(暂名)**灵感来源于一款象棋游戏。这是一个简单的游戏。然而,我在这一系列文章中的目的并不是告诉 step 如何有效地开发、设计游戏。而是将重点放在如何为 AI 算法的输入创造一个环境。我不会用 Unity 或者虚幻引擎之类的游戏引擎。但是,我希望我的代码可以帮助你用强大的人工智能设计你的棋盘游戏,不管你是否使用游戏引擎。
I do it in my free time. Source : https://pixabay.com/en/coffee-magazine-newspaper-read-791439/
注:这部分还没有用到深度学习,但是会涉及到如何创建用于对抗性搜索的 AI 环境的基础 AI。这部分将集中在设计游戏的类上。如果你只想看到人工智能的运行,跳过这一部分。
很抱歉,我还不能涵盖深度学习部分。我怕会让我的文章很长,读起来没意思。我想一步步分享我的知识。所以我会写在后面的部分。
这篇文章的目标读者是对人工智能和设计游戏感兴趣的人。如果你不是其中之一,当然你仍然可以看到这篇文章(谁告诉你你不能😆).我希望通过发表这篇文章来提高我的写作技巧,并且内容对你有益😄。
简介
谁不喜欢游戏呢?这是一种人们用来解闷的活动。游戏很有趣,可以训练我们的大脑思考如何解决问题,尤其是棋盘游戏。棋盘游戏是一种在棋盘上移动棋子的策略游戏。有几种棋盘游戏,如跳棋、国际象棋和大富翁,需要两个或更多的玩家来玩。击败对手需要好的策略。制定一个击败对手的好策略需要时间。
Source : https://pixabay.com/en/chess-board-game-fireside-strategy-2489553/
现在,棋盘游戏是在电脑上玩的。它通常有一个人工智能代理,让玩家练习游戏。人工智能有一个策略来寻找最好的移动。当然,人工智能越聪明,我们就越喜欢打败人工智能。所以,我们在游戏中制造一个强大的人工智能来学习好的策略怎么样?它将使人工智能足够强大,让人类享受游戏或让他/她沮丧😆。如果你根据自己的游戏想法创造的人工智能被人们所喜欢,那感觉真好。
这篇文章将集中在我如何创造我自己的原创桌游创意evopheness(临时名称)。我还没有检查我的游戏是否已经存在。我认为没有。代码将专注于设计游戏的基本类,准备成为人工智能算法的输入。因为喜欢 Python,所以我会用 Python 语言来写。当然你可以用你喜欢的游戏引擎来写。如果你想实现它,你应该了解如何创建本文中描述的游戏的流程。环境是基于彼得·诺维格的人工智能书。
走吧,让我们创造游戏!
概述
本文将按以下顺序撰写:
- 技术
- 游戏的描述和规则
- 术语定义
- 我们的管道或步骤
- 定义我们的游戏
- 履行
- 结论
- 编后记
技术
在本文的项目中,我们将使用这些:
- Python 3.6.5
- PyCharm IDE 或首选文本编辑器,如 Atom 或 Sublime(可选)。
目前不需要其他库。稍后,在即将发布的文章中,我们将使用 TensorFlow(也可能是 PyTorch)来开发 AI agent。
游戏描述和规则
EvoPawness(暂名)是我想出来的一款桌游。这是一个由两个玩家玩的回合制游戏。这块木板有 9 x 9 块瓷砖。每个玩家有 6 个不同颜色(黑色和白色)的棋子。每个玩家有 5 个士兵棋子和 1 个国王。这个游戏的目标是摧毁对手的国王或所有敌人的棋子。
每个棋子都有生命值、ATK 和步数。HP 表明他们的功能或生存能力。ATK 是一个减少敌人设定数量的 ATK 点的属性。步点表示棋子基于其独特移动可以移动多少步。还有一个棋子状态,它表明棋子是否已经被激活。如果它没有被激活,它就不能移动、攻击或进化。
Image 1 : Illustration of the EvoPawness board game
士兵卒可以进化成更高等级的卒。士兵卒可以进化成骑士、车和主教。骑士和主教可以进化成女王。他们会有不同的能力。以下是每个棋子的能力:
士兵
Soldier Pawn. Source : https://pixabay.com/en/chess-pawn-white-chess-board-parts-56257/
移动:士兵只能向前移动。步数取决于步点。可以绕过对手的兵。
惠普,ATK,步骤:默认 3,1,1
**攻击目标和范围:**和动作一样,只能依靠步点向前攻击。棋子不会从原来的位置移动。
进化:可以进化成骑士、车和主教
注:如果棋子被激活,可以移动、进化和攻击
骑士
Knight Pawn. Source : https://pixabay.com/en/graphic-sign-symbol-logo-icon-3608411/
动作:各个方向呈 L 形(像象棋版)。步数固定为 1(忽略步点数)。可以绕过对手的兵
生命值,ATK,第步:进化为骑士前的点数属性(如果是生命值,将是当前生命值而不是最大生命值)加上这些点数(0,4,0)
**攻击目标和范围:**同样喜欢运动。棋子不会从原来的位置移动。
进化:不能进化
车
Rook Pawn, Source : https://pixabay.com/en/chess-figure-rook-black-checkerboard-3413419/
移动:可以前、后、左、右移动(像下棋一样)。步数基于步点数。可以绕过对手的兵
生命值,ATK,第步:进化为骑士前的点数属性(如果是生命值,将是当前生命值,而不是最大生命值)加上这些点数(2,2,0)
**攻击目标和范围:**同喜欢运动。棋子不会从原来的位置移动。
进化:进化成女王
主教
Bishop Pawn, Source : https://cdn.pixabay.com/photo/2012/04/18/00/52/chess-36348_1280.png
移动:对角线向各个方向移动(像下棋一样)。步数基于步点数。可以绕过对手的兵
生命值,ATK,第步:进化为骑士前的点数属性(如果是生命值,将是当前生命值,而不是最大生命值)加上这些点数(2,1,1)
**攻击目标和范围:**同喜欢运动。棋子不会从原来的位置移动。
进化:进化成女王
女王
Queen Pawn, Source : https://pixabay.com/en/chess-chess-peace-game-3d-680492/
移动:对角线向各个方向移动,可以前、后、左、右移动(像下棋一样)。步数基于步点数。可以绕过对手的兵
生命值,ATK,第步:进化为骑士之前的点数属性(如果是生命值,则为当前生命值,而不是最大生命值)加上这些点数(2,2,0)
**攻击范围:**同喜欢运动。棋子不会从原来的位置移动。
进化:无法移动
国王
King, Source : https://pixabay.com/en/chess-king-figure-game-black-159693/
移动:不能移动
惠普,ATK,步长:默认值(15,4,1)
**攻击范围和目标:**像女王一样用一个范围攻击。棋子不会从原来的位置移动。
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
每个玩家都有一种特殊的力量,需要“法力”来实现。“法力”每回合产生一次(最多 10 次)。在第一轮,他们将有 5 个马纳。它将在每回合一次生成。这里有特殊的力量:
- 激活一个士兵卒:需要 3 个马纳
- 进化兵卒:需要 5 个玛纳斯
- 进化一辆车和一个主教:需要 10 个马纳
最后,有两个“符文”随机产生。它会在每个玩家的区域附近随机产生。如果符文被棋子接近。卒可以在他们的一个属性上获得加值(随机)。奖金是:
- 气血增加 2
- 增加 1 级
- 增加 ATK 2
符文可以在棋子的位置上产生。如果它在棋子所在的位置繁殖,它将立即获得奖励。这条规则会让游戏变得不确定。如果我们使用的算法需要游戏是确定性游戏,我们会改变这个规则。
每个玩家每回合只能选择一个动作。他们必须选择移动棋子或激活特殊异能。如果没有可以采取的行动。玩家必须跳过这个回合。
Source: https://pixabay.com/en/whistle-black-blow-referee-game-33271/
黑人玩家总是第一个移动。所以在第一个回合,轮到黑人玩家,然后轮到白人玩家。他们将交替轮流。
这些规则可以根据我们在下一部分使用的人工智能算法而改变。我以后会告诉你我们是否需要改变某个特定的部分。我还没有测试游戏的平衡。所以,不要指望游戏会保持平衡。可能有一种策略被压倒了(OP)。我们不会讨论游戏的平衡。但是如果你认为这个游戏真的不平衡,就告诉我。我会根据你的声明改变游戏规则。
这就是赢创(临时名称)的全部规则。如果您对这个名字有疑问,那么“(临时名称)”也是这个名字的一部分。我还没有决定游戏的名字,所以它将是临时的名字。
术语定义
初始状态
指定游戏开始时是如何设置的。
行动。输入=(状态)
返回一个州的一系列合法举措
结果函数。输入=(状态,动作)
一个转换模型,它将定义给定状态的动作结果。
终末试验。输入=(状态)
检查游戏的终端状态。如果游戏已经结束,则返回 true,否则返回 false
实用功能。输入=(状态,播放器)
定义玩家处于终端状态时游戏的最终数值。数值公式是由我们定义的。
评估功能。输入=(状态,播放器)
定义玩家在给定状态下对预期效用数值的估计。这个函数在游戏还没有结束的时候被调用。
我们的管道或步骤
我们的步骤如下:
Image 2 : our pipeline
我们已经陈述了游戏的描述和规则。我们仍然需要定义游戏的状态、初始状态、玩家、可能的动作、结果函数、最终测试、效用函数和评估函数。然后,我们需要定义游戏的元素或对象。然后我们设计类图并实现它。哦,我跳过了动作功能。我们将在初始状态之后定义它。
在本文中,我们将跳过效用函数和评估函数。我们将在下一篇文章中定义它们。
定义我们的游戏
在这一部分,我们将定义所有我们必须定义的游戏元素。如上所述,它是基于彼得·诺维格的书。让我们跟随它:
国家代表权
我们应该定义我们将如何表现游戏的状态。在此之前,我们应该决定在状态中应该保存哪些信息。在这个游戏中,有:
格式:游戏的对象(属性)
- 转动计数器
- 玩家(法力,颜色)
- 棋子(生命值,攻击力,位置,活动状态,彩色玩家,步,死亡)
- 王者(hp,atk,位置,彩民,步,死)
- 符文(奖励,职位)
好了,我想就这些了。让我们想想如何代表他们。
我们将需要一个 2D 名单或阵列的棋子,以保持坐标的棋子和国王的位置信息的游戏。它可以使我们更容易检查棋子的位置。因此,我们将把我们的棋子和国王的位置写入一个 2D 列表或棋子和国王的数组。
我们还需要一个玩家、兵、符文和国王的列表来跟踪每个对象。仅仅为了检查每个对象的位置而循环我们的 2D 数组是令人疲惫的。
最后还有一个转计数器。这将帮助我们决定现在轮到哪一轮了。如果该指示物能被 2 整除,则轮到白牌手,否则轮到黑牌手。
可能有比这更好的表述。但我认为这将足以代表我们的游戏。
初始状态(状态)
这是我们基于如何表示状态的初始状态:
Image 2: Initial state with 2d board
其中的棋子列表与我们的 2D 阵棋盘中的棋子相同。所有棋子的状态都是不活动的(状态布尔值为假)
玩家(功能(状态)->玩家 _ 索引)
有两个玩家,白人玩家和黑人玩家。玩家的回合决定于:
如果回合是偶数,则是白牌玩家回合。但如果回合是奇数,则轮到黑色玩家。
动作(功能(状态)->动作列表)
每个参与者都可以完成以下操作:
玩家
如果玩家有 5 个马纳,他/她可以将一个士兵卒进化成更高级的卒(女王和骑士除外)。
如果一个玩家有 3 个马拿,他/她可以启动一个兵卒。
如果一个玩家有 10 个马拿,他/她可以将车或象兵进化成女王。
卒
如果可能的话,棋子可以根据棋子的种类移动到指定的方向。
如果敌人的兵在攻击范围内,兵可以攻击敌人的兵。
君王
如果敌人的兵在攻击范围内,盟友的王可以攻击敌人的兵。
Image 3: rough scratch action flow
通过
如果没有可用的动作,通过该回合。
所有可能的动作将被追加到一个将由函数返回的列表中。
结果函数(函数(状态,动作)->新状态)
这是我们行动的结果
有三位演员我们必须去看:
- 玩家
玩家可以用马纳斯的费用来进化棋子。它会将棋子变成更高级的棋子。它将添加属性
玩家可以用马纳斯的费用启动兵。它会将棋子状态更改为活动。
- 卒
兵可以根据类型移动。它将改变棋子在棋盘阵列中的位置。如果棋子命中一个符文,会随机增加其属性(只有一个)。
兵可以通过根据攻击者的 ATK 点减少生命值来攻击对手的兵。
- 君王
棋子可以通过根据攻击者的 ATK 点数减少生命值来攻击对手的棋子
- 过关
通过转弯。
Image 4 : Result Function
完成上述动作之一后,回合增加一。如果该回合能被 5 整除,它将产生 2 个随机符文。
终端测试(Function (state) -> boolean true or false)
它检查游戏是否结束。
我们会检查每个国王的死亡属性。如果为真,那么函数将返回**true**
。如果没有,我们将检查每个玩家的棋子。
如果玩家没有活着的棋子,该函数将返回**true**
,否则返回**false**
。
效用和评价函数(函数(状态,播放器)->整数)
我们不会在这里公式化我们的评价或效用函数。在当前的文章中不需要。
履行
在我们编码之前,我们需要设计我们的类,使我们的代码变得更加结构化。我们需要设计这种关系。我们将在模型视图控制器(MVC)设计模式中完成。
Image 5: Class Diagram.
所有类型的棋子将继承棋子抽象类。国家将有符文,球员,和卒类。这些是我们的模型。我们定义的元素在游戏控制器中。
就是这样,状态意志包含符文,玩家,兵。游戏控制器将成为视图和状态之间的桥梁。控制器将生成我们上面定义的所有元素。状态是我们的模型,将由 GameController 类处理。
源代码将被上传到我的 GitHub 库。我们不会在这里讨论所有的代码。我们将讨论如何基于我们定义的元素进行编码。
国家代表权
以下是我如何对州进行编码的示例:
它具有我们上面定义的所有属性。
初态
这是如何定义初始状态的例子
演员
下面是决定谁该轮到谁的代码。
class State:
def get_player_turn(self):
return self.turn % 2class GameController:
def player(self, state):
return state.get_player_turn()
行动
下面是决定可能的操作的代码。我将向您展示如何生成可能的操作的示例。
它会返回玩家可能的动作。还有棋子可能行动的代码。但是,我不会在这里展示它,因为它很长。可能的动作将通过 dict 格式返回,其中包含棋子的信息和玩家的信息。它将包含有关该操作的所有信息。
这里有一个使用可能动作的例子。这是玩家可能的动作之一(记住,玩家有两个可能的动作,提升和激活) :
'p1a4’: {‘action’: ‘activate’,
‘pawn_atk’: 1,
‘pawn_hp’: 3,
‘pawn_index’: 4,
‘pawn_step’: 1,
‘pawn_x’: 8,
‘pawn_y’: 1,
‘player_index’: 1}}
其中“p1a4”是唯一键,用于标识唯一动作。
控制器将调用该函数。
class GameController:
def get_possible_action(self,state):
all_possible_action = self.combine_action(self.state.get_possible_action() + self.state._get_possible_action_pawn() + self.state._get_possible_action_king())
if len(all_possible_action.keys()) == 0:
return [{'action': {'skip' : {'action': 'skip'}}}]
return all_possible_action
结果函数
这是结果函数的例子
它将接收动作输入和当前状态。例如,我们想要激活索引为 1 的棋子,该函数将接收以下输入:
{‘action’: ‘activate’,
‘pawn_atk’: 1,
‘pawn_hp’: 3,
‘pawn_index’: 4,
‘pawn_step’: 1,
‘pawn_x’: 8,
‘pawn_y’: 1,
‘player_index’: 1}
它将激活一个棋子并返回新状态。小心,我们必须确保首先复制我们的状态,以避免对象引用(使用 deepcopy 库)。
该函数将响应控制器调用的方法。
终端功能
下面是如何检查游戏是否已经结束的例子
该功能将检查卒和王的死亡状态。它没有告诉我们谁是赢家。它只会检查游戏的终端状态。
测试游戏
我还没有创建 GUI 版本。目前,我们只能在终端或命令提示符下玩。在候机厅玩不舒服。下面是我玩游戏的截图:
Sorry for my bad writing >_<
状态、可能的动作将是我们视图的输入。
我们视图的代码将被放在 GitHub 存储库中。
结论
我们从零开始创造了一个新游戏。我们已经定义了一些元素,可以作为人工智能算法的输入。我们定义了状态、初始状态、玩家函数、结果函数和终端函数的表示,并用 Python 编程语言编写。
我还没有测试过代码是否能完美运行。所以,如果有人发现了 bug,可以在这里留言。
编后记
感谢您阅读我关于人工智能的第二篇文章。我需要一些建设性的反馈,让我在写作和人工智能方面做得更好。请手下留情😆!
我只是想把我的知识分享给读者。分享知识很好,因为它可以帮助有需要的人。我正在学习这个领域,想分享我所学到的东西。如果有人告诉我这篇文章有什么问题,那就太好了。
GitHub 库将于明天在创建。我需要先记录这个函数。
编辑:这是 GitHub 链接
我希望 GUI 能在下一篇文章中完成。在候机厅玩真的很难受。如果有人想为创建 GUI 做出贡献,我会非常感激😃。
原谅我低效的代码,我已经尽量让代码可读,高内聚,低耦合。因此,欢迎任何反馈来修复我的代码混乱。
如果你想从我这里得到另一篇这样的文章,请拍下这篇文章👏 👏这会鼓舞我写下一篇文章的精神。我保证会做出更好的关于 AI 的文章。
在下一篇文章中,我将分享一个关于对抗性搜索的传统算法。在此之后,我们将进入创建具有深度神经网络的代理。
下一篇文章再见!
Source : https://cdn.pixabay.com/photo/2017/07/10/16/07/thank-you-2490552_1280.png
系列文章
第 1 部分:从头开始为你自己的棋盘游戏创建人工智能——准备——第 1 部分
第二部分:从零开始为你自己的棋盘游戏创造人工智能——Minimax——第二部分
第三部分:为你自己的棋盘游戏从头开始创造人工智能——alpha zero——第三部分
来源
一种在棋盘上移动棋子的策略游戏(如跳棋、国际象棋或西洋双陆棋);一种游戏(如国际象棋)…
www.merriam-webster.com](https://www.merriam-webster.com/dictionary/board%20game) [## UML 类图教程
为每个想学习类图的人编写的全面的 UML 类图教程。阅读此 UML…
www.visual-paradigm.com](https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/)
拉塞尔,斯图尔特 j,和彼得诺维格。人工智能:一种现代方法。普伦蒂斯霍尔出版社,2010 年。
通过 Python 使用 API 创建数据集
“person using laptop” by rawpixel on Unsplash
每当我们开始一个机器学习项目时,我们首先需要的是一个数据集。虽然您可以在网上找到许多包含各种信息的数据集,但有时您希望自己提取数据并开始自己的调查。这时,网站提供的 API 就可以派上用场了。
应用程序接口(API)是允许两个软件程序相互通信的代码。API 为开发人员定义了编写从操作系统(OS)或其他应用程序请求服务的程序的正确方式。— 技术目标
API 实际上是一个非常简单的工具,允许任何人从给定的网站访问信息。您可能需要使用某些头,但是有些 API 只需要 URL。在这篇特别的文章中,我将使用由自由代码营 Twitch API 直通提供的 Twitch API。这个 API 路由不需要任何客户端 id 来运行,因此访问 Twitch 数据非常简单。整个项目在 Create-dataset-using-API 存储库中作为一个笔记本提供。
导入库
作为访问 API 内容和将数据放入. CSV 文件的一部分,我们必须导入一些 Python 库。
- 请求库通过使用
get()
方法帮助我们从 API 获取内容。json()
方法将 API 响应转换为 JSON 格式,以便于处理。 - 需要 json 库,这样我们就可以使用从 API 获得的 JSON 内容。在这种情况下,我们为每个频道的信息(如名称、id、视图和其他信息)获取一个字典。
- pandas 库帮助创建一个数据帧,我们可以用正确的格式导出一个. CSV 文件,带有适当的标题和索引。
了解 API
我们首先需要了解从 API 中可以访问哪些信息。为此,我们使用 channel Free Code Camp 的例子来进行 API 调用,并检查我们获得的信息。
This prints the response of the API
为了访问 API 响应,我们使用函数调用requests.get(url).json()
,它不仅从 API 获得对url
的响应,还获得它的 JSON 格式。然后,我们使用dump()
方法将数据转储到content
中,这样我们就可以在一个更直观的视图中查看它。代码的输出如下所示:
Response for API
如果我们仔细观察输出,可以看到我们收到了大量信息。我们得到的 id,链接到各种其他部分,追随者,姓名,语言,状态,网址,意见和更多。现在,我们可以遍历一个频道列表,获取每个频道的信息,并将其编译成一个数据集。我将使用列表中的一些属性,包括 _id 、显示 _ 名称、状态、关注者和视图。
创建数据集
现在,我们已经知道了 API 响应的内容,让我们从一起编译数据和创建数据集开始。对于这个博客,我们将考虑我在网上收集的频道列表。
我们将首先从定义数组中的通道列表开始。然后,对于每个通道,我们将使用 API 获取其信息,并使用append()
方法将每个通道的信息存储在另一个数组channels_list
中,直到我们将所有信息收集到一个地方。请求响应是 JSON 格式的,所以要访问任何键值对,我们只需在JSONContent
变量后面的方括号中写下键值名。现在,我们使用 pandas 库,使用 pandas 库中提供的方法DataFrame()
将这个数组转换成 pandas 数据帧。dataframe 是一种类似于表格的表格形式的数据表示,其中数据以行和列的形式表示。该数据帧允许使用各种方法快速处理数据。
Create dataframe using Pandas
pandas sample()
方法显示随机选择的数据帧行。在这个方法中,我们传递我们希望显示的行数。这里,让我们显示 5 行。
dataset.sample(5)
仔细观察,我们发现数据集有两个小问题。让我们逐一解决它们。
- **标题:**目前,标题是数字,并不反映每列所代表的数据。对于这个数据集来说,这似乎不太重要,因为它只有几列。然而,当您要探索具有数百个列的数据集时,这一步将变得非常重要。这里,我们使用 pandas 提供的
columns()
方法定义列。在这种情况下,我们明确定义了标题,但在某些情况下,您可以直接将关键字作为标题。 - **无/空/空白值:**某些行将会有缺失值。在这种情况下,我们有两种选择。我们可以删除任何值为空的整行,也可以在空白处输入一些精心选择的值。这里,
status
列在某些情况下会有None
。我们将通过使用方法dropna(axis = 0, how = 'any', inplace = True)
删除这些行,该方法删除数据集中包含空值的行。然后,我们使用方法RangeIndex(len(dataset.index))
将数字的索引从 0 更改为数据集的长度。
Add column headings and update index
导出数据集
我们的数据集现在准备好了,可以导出到外部文件。我们使用to_csv()
方法。我们定义两个参数。第一个参数指的是文件的名称。第二个参数是一个布尔值,表示导出文件中的第一列是否有索引。我们现在有了一个包含我们创建的数据集的. CSV 文件。
Dataset.csv
在本文中,我们讨论了一种使用 Twitch API 和 Python 创建我们自己的数据集的方法。您可以遵循类似的方法通过任何其他 API 访问信息。使用 GitHub API 试试看。
请随意分享你的想法,如果你有任何问题,请打电话给我。
为 Planet Coaster 中的平顺性动画创建重力分析工具
“平板游乐设施”一词描述了主题公园中除过山车之外的所有游乐设施,包括摩天轮和其他大型液压怪兽等大型景点,这些怪兽会让人们在周围摇摆以取乐。重要的是,我们在制作《星球飞车》的时候把这些游乐设施做对了,不仅仅是造型细节,还有动作。
在开发的早期阶段,我们试验了各种物理驱动的系统,试图重现准确的平顺性运动,但最终结果往往是不可预测的,并且没有公正地对待我们用作参考的现实生活中的景点。我们很早就决定,最好的方法是手工制作平车(不是过山车)动画,尽可能让它们接近原始参考视频。这使我们能够复制每一个细微的细节,同时保持对所有循环动画的绝对控制,最终形成一个平稳的游乐设备运动序列。
A guest reacting to lateral G-force at speed
游乐设施开始进入游戏,很快我们就能看到游客坐在座位上的游乐设施。为了让这些游客栩栩如生,他们需要对游乐设施可能带他们经历的所有运动做出逼真的反应,为此,我们需要一种从他们的经历中提取详细遥测信息的方法。
This is what we’re going to work towards
我希望能够捕捉游客在游乐设施上感受到的垂直、横向和水平重力,以及其他有用的统计数据,如高度和速度。我需要的所有信息都出现在动画中,所以我开始编写一个工具,可以分析 Maya 场景并挖掘我们需要的信息来驱动一套反应性的来宾动画(向左倾斜、向右倾斜、加速、停止等)。这种工具还可以标记出不适合主题公园骑行的高重力热点。
This early development video gives you a glimpse of the tool we’ll be talking about today
一开始,我只是在游乐设施上选择一个可以代表游客的位置,并为其设置一个定位器。这将作为我的“客人”位置,我可以从中提取我需要的所有数据。
Set your calculators to ‘maths’
获得基础知识
所以,我有一个定位器,在这个场景的动画中,它代表一个游客被带到一个游乐设施上。最简单的形式是,这个工具需要在动画的每一帧上捕捉游客定位器的变换矩阵,然后我们就可以从那里计算出剩下的部分。
**for** i, time **in** enumerate(timeSliderRange):
pm.currentTime(time)
matrix = TransformationMatrix(obj.worldMatrix.get())
position = xform(obj, q=True, rp=True, ws=True)
positions.append(Vector(position))
matrices.append(matrix)
我将定位器的转换矩阵存储在一个单独的列表中,与它的旋转-枢轴位置相对应,以使只需要位置值的代码更容易编写。我会在这里放一些代码片段,但是它真正的上下文在文章末尾的主代码块中有详细的描述。
载体
在接下来的一点,你会看到我使用了 PyMel 的‘Vector’类相当多,所以我想我应该给出它们是什么以及我为什么使用它们的粗略概述。
Not that Vector
简而言之,它们是代表空间位置的三个值(在 3D 中),例如 x,y,z 。
为了形象化,假设向量([0,0,0])表示场景中心的位置,但是向量(0,1,0)表示在 Y 轴上距离场景中心一个单位的位置。第二个向量与第一个向量的关系描绘了一条直线上升的轨迹。
在下面的例子中,我举例说明了一个值为 5、4 和 2 的向量,它转化为一条与读者成一定角度的直线。
所以使用多个向量,你可以计算出一个物体的位置和方向。对这个问题很有用。
物理时间
是时候重温普通中等教育证书物理了。我可以假设:
- 重力=加速度/重力。
- 为了得到加速度,我们需要算出速度/时间
- 为了得到速度,我们需要计算距离/时间
我们有了计算距离/时间所需的所有位置,所以我从计算动画过程中游客的速度开始。
计算客人的速度
δt(δ时间)正好是 1.0 除以场景的帧速率,因此重新排列该公式的一个更简单的方法是。
δ表示“在 x 中的变化”,所以δt代表从时间轴的一帧到下一帧的变化。
行进的距离将是我们的定位器在当前帧上的位置减去其在前一帧上的位置。
oldPos = positions[0]
**for** i, position **in** enumerate(positions):
**if** i == 0: **continue** velocity = (position - oldPos) * frameRate
velocities.append(velocity)
oldPos = position
我们只是遍历位置列表(排除第一个位置,因为它没有之前的帧可以比较),计算偏移量并将其乘以帧速率,然后将速度存储在列表中。
计算游客的加速度
让我们再一次重新排列这个公式来简化它,其中δv(速度的变化)是前一帧的速度与当前帧的速度之差。
velocities[0] = velocities[1]
# Acceleration
accelerations = [dt.Vector(0, 0, 0)]
oldVelocity = velocities[0]
**for** i, velocity **in** enumerate(velocities):
**if** i == 0: **continue** acceleration = (velocity - oldVelocity) * frameRate
accelerations.append(acceleration)
oldVelocity = velocity
为了让这些数据有用,我需要更多关于游客加速方向的信息。要算出来,我需要客人的总体加速度的上、前、右矢量的“点积”。
fAcc = []
uAcc = []
rAcc = []
**for** i, acceleration **in** enumerate(accelerations):
matrix = matrices[i]
forwards = dt.Vector(matrix[0][0:3])
up = dt.Vector(matrix[1][0:3])
right = dt.Vector(matrix[2][0:3])
fAcc.append(forwards.dot(acceleration))
uAcc.append(up.dot(acceleration))
rAcc.append(right.dot(acceleration))
什么是点积?
在这一阶段之前,我们计算了游客在世界空间中的加速度,这意味着我们知道他们在任何方向上都加速了一定的量。下一步是通过将其分解为相对于游客在每个方向上的加速度来解释这一加速度。
我做的第一件事是从访客定位器的转换矩阵中获取前向向量,并将其转换为向量数据类型。PyMel 的内置 vector 类型已经有了计算点积的方法。
forwards = dt.Vector(matrix[0][0:3])
在下一步中,我们使用这种方法沿着它的前向轴投射游客的加速度。产生的点积是客体在该轴上加速的量。
forwards.dot(acceleration)
Calculating the Forward and Up acceleration
关于的图解释了我在 2D 的情况下做了什么,其中游客的向量被描述为 v 和加速度被投影并绘制在向前的轴上。加速是点积,它向前加速的量,而加速是它向上加速的量。
好吧,回到获取重力的话题。
计算我们的客人受到的重力
最后一步是计算施加在游客身上的重力。我这样做是通过将定位器的加速度除以重力(9.81)。
使用矢量‘length()’方法计算速度和加速度
我在主代码块的末尾放了一些我还没有提到的东西,所以我将快速描述一下它的内容。
PyMel 的 Vector 类中还包含了一个非常方便的“length()”方法,用于计算向量的长度。如前所述,我们将速度和加速度值存储为向量,这允许我们对它们执行各种预建的数学函数。
如果我们以之前的向量为例,将其表示为直角三角形,我们可以将其转置到 2D 平面上,并将边表示为 a、b 和 h (斜边)。从这里我们可以应用毕达哥拉斯定理,将这个向量的长度表示为:
通过找到一个向量的长度(或大小),我们可以将一个物体的速度和加速度解释为一个单浮点数而不是三个值。
代码时间
我制定并测试了所有这些不同的方法,所以我决定将所有这些组合成一个简单的 Python 类,名为“SceneAnalysis ”,它将有一个“analyse()”方法,用于在一个定义的对象上运行所有这些循环,并将结果存储在其自身中。
然后,我可以将这个类传递到一个 UI 中,让它在以后构建图形或导出元数据。
结论
The Flatride Analyser doing its thing
工具起作用了。一旦我收集了所有的数据,我花了一些时间在 PySide 上构建了一个 UI,它可以将所有的值显示为曲线。
最终,我们没有使用这个工具来生成动画系统解释的数据。我们决定,数学足够简单,可以在运行时计算,处理开销很小,并且仍然驱动反应式动画系统。这样做还允许我们将同样的数学方法应用于杯垫(可以是任何形状、速度或大小,因此无法预先计算),还允许我们迭代动画,而不需要维护单独的元数据文件。我们确实保留了用手写数据覆盖这个系统的能力,以纠正动画看起来不太正确的特殊情况,这在一些最复杂的游戏中派上了用场。
All the forces are displayed in this UI, with the Horizontal, Vertical and Forward G-Forces represented by Red, Green and Blue (x, y, z)
那么,制造这种工具值得吗?当然可以。鉴于我们的初始解决方案是手工创作这些数据,原型制作系统证明了它足够简单,可以自动完成,并为我们的动画师节省数小时的时间。
该工具也是一个优秀的明智检查器,允许我们分析我们的工作,标记任何客人的重力会飙升到致命程度的时刻,并给我们一个机会来相应地调整我们的骑行动画。
总的来说,这是一个非常有趣的制作工具(如果你和我一样喜欢图形),也是一个仔细观察我们手工制作的动画背后的物理原理的好方法。
一如既往,特别感谢詹姆斯·奇尔科特和蜜琪拉·斯特雷特菲尔德的数学专长