构建我的第一个机器学习模型| NBA 预测算法
融合体育分析和机器学习的世界
马库斯·斯皮斯克在 Unsplash 上的照片
**编辑:**自从写了这篇文章,我们已经在https://infinity sports . ai推出了订阅服务,可以访问我们新的 NBA 预测模型的 API 和输出。我们现在能够预测赢家、差价和总点数。我非常喜欢在这个项目上工作,请随时与任何问题联系!
当我几年前开始编程的时候,我从来没有想过要用我新学到的技能做什么。我玩着制作视频游戏,制作基本的网站,iOS 应用程序,但在某种程度上,我没有专攻某一门手艺,分散了自己的精力。大约在这个时候,多伦多猛龙队进入了历史性的季后赛,我刚刚开始玩 bet365,碰巧在整个季后赛期间专门押注于他们,获得了丰厚的利润。
鉴于我在商业和计算机科学方面的背景,我想更多地了解定量分析师和数据科学家的角色。鉴于我的 python 背景,我开始探索数据科学的世界,并从学习 Scikit-learn 包的基础开始。本文的其余部分将概述我是如何从对数据科学和机器学习几乎一无所知,到构建我的第一个 NBA 预测模型,准确率约为 72%(稍后将详细介绍,但结果并不像看起来那么好)。
目录
- 游戏计划
- 数据采集
- 数据探索&处理
- 选择合适的型号
- 测试和结果
游戏计划
在过去的 10-20 年里,NBA 以及其他许多体育项目已经见证了统计学的应用呈指数级增长。我通过阅读 开始搜索最相关的 NBA 统计数据,NBA 统计数据实际上转化为胜利 作者 Chinmay Vayda 。他的研究发现,NBA 获胜的最佳预测因素是球队的进攻得分、防守得分、篮板差、3.5%以及其他数据,你可以通过上面的链接了解更多信息。
我计划使用更近的数据,通过利用 NBA 的月度统计数据,并使用这些数据作为在那个月进行的比赛的预测。因为这是我第一次尝试建立一个模型,所以我想保持数据简单,并保持数学复杂性最小。
下一步是弄清楚如何获取这些数据。我需要一个过去 10 年比赛结果的数据源,以及一个球队在任何给定月份的统计数据。
数据采集
对于这个项目的数据获取部分,我使用了 Selenium ,这是一个方便 web 抓取的 python 包,用于从各种网站获取我需要的数据。我还决定将我的搜索限制在从 2008-2009 赛季至今的数据上。
过去的结果
我过去比赛结果的数据来源是 Basketball-Reference.com 的比赛结果,可以追溯到 1946 年。
Basketball-Reference.com上的数据源截图
用 Selenium,我把这些表格一张张刮下来,转换成 CSV 文件,以备后用。
过去的月度统计
我还需要知道每个月每个球队的统计数据,我的数据来源是 NBA 官方统计页面。在调整了一些过滤器之后,我最终得到了下表。
NBA 高级统计数据上的数据源截图
我又一次使用 Selenium,把这些月度统计表刮下来,保存为 CSV 文件。我的下一步是将这些文件合并成一个数据集。
数据处理和探索
数据处理
我们现在必须以一种可以进行比较和分析的格式处理我们的 2 组 CSV 文件。我在下面嵌入了每个 CSV 文件的前 5 行的例子,正如它们被废弃一样。
原始形式的结果和统计数据的前 5 行。
我们注意到的第一件事是,两个表都没有标题,这需要通过引用原始表来手动添加。其次,我们的数据集中有多个空列,需要删除它们。最后,在我们的结果数据集中,我们需要删除时间列、框分数文本和出席人数,因为它们对我们没有用。
按照这些步骤,我们的数据集现在看起来像这样:
结果和统计后处理的前 5 行。
下面的代码概述了我如何着手合并这两个 CSV 文件,以及添加一个新列来表示团队 1 是赢还是输,这将成为我们的预测变量。我贴上代码是因为合并的细节不容易解释,但是如果你感兴趣,你可以看看下面。
执行上述代码后,我们的数据集现在看起来像这样:
我们现在有了我们的数据集,它比较了每个队的统计数据以及哪个队赢得了这场遭遇战。在这个过程的最后,我们的数据集包含超过 13,000 场独特的比赛!这个数据集现在非常接近于为机器学习分析做好准备。
由于我只需要团队名称和分数来合并数据集,并找出哪个团队赢了,所以我也将删除这些列,留下一个数据集,现在可以对其进行探索和建模以进行机器学习。
数据探索
我们当前的数据集如下所示:
我现在有兴趣知道我们数据集中的各种统计数据是如何相互关联的。我的一个朋友向我介绍了一个名为 Speedml 的伟大的 python 包,它简化了探索性分析的过程,并生成了非常好看的图表来共享您的数据。
当您有数字要素时,查看数据集中的每个要素如何与其他要素相关总是很有趣的。下图说明了我们的特征相关性,它有一些我们可以得出的有趣见解。
Speedml 生成特征关联图
首先,我们可以看出,因为左上和右下象限具有最相关的特征,所以团队的统计数据与其自身有很强的相关性。如果一个团队的节奏很快,它可能也会有很高的效率。
我们还看到,“Team1Win”功能与任何特定功能都没有很强的相关性,尽管有一些功能可能比其他功能具有更强的相关性。这让我创建了一个特性重要性图,这是 Speedml 中包含的另一个易于使用的实现。
团队统计特征重要性
虽然我们确实看到了一些更重要的特性,但我们没有明确的理由在这一点上消除任何特性,但它允许进行一些有趣的观察。
Team2NETRTG vs Team1Win 的带状图
从上面的带状图中可以看出 Team2NETRTG 如此重要的原因;当团队 2 的净评分较高时,团队 1 赢得的机会往往会少于,反之,当团队 2 的净评分较低时,团队 1 获胜的机会往往会更高。
客队胜率
最后,我觉得有趣的最后一个情节是队 1 胜的分布。我们获取数据的方式是,团队 1 总是客队。这种分布显示,客场球队输掉了大约 59%的时间,这说明了我们所知道的主场优势。
选择正确的模型
我选择使用 Scikit-learn,因为它易于实现各种算法,并且我有使用 python 的经验。使用 Scikit-learn 提供的流程图,我确定了几个我想尝试的模型,并查看哪一个在我的数据集上表现得最准确。我的尝试开始如下:
在所有尝试的模型中,准确率最高的是支持向量机 SVC 分类器,对历史数据的准确率为 72.52%!现在是时候在现实世界中测试这个模型了,在新冠肺炎事件导致 NBA 停摆之前,我尝试预测了 67 场比赛的结果。
测试和结果
在我的前 28 场比赛预测中,我只是简单地说出谁会赢,没有考虑我得到的差距。在我预测的剩余时间里,我实现了一种计分方法,根据哪支球队更有可能获胜,给出了我的算法创建的一个分布。我的算法准确性结果如下:
我的算法的一个主要弱点是预测混乱的能力。在运行我的模型的第一天,7 场比赛中有 5 场是失败者赢了比赛。我的算法通常与在线体育博彩网站发布的内容一致,这是一个好迹象。在网上阅读,我们可以看到 NBA 通常有32.1%的爆冷率,而我的样本量有 40.2%的爆冷率。
我还想尝试一种财务策略,并开始简单地在预测的赢家身上下注。但是在更新了我的模型之后,我实现了一个货币线,并与 bet365 提供的货币线进行了比较。我比较了我和 bet365 的赔率,选择了两者中更有利的一个。下面我们可以看到盲目和选择性策略的结果。
我们可以立即看出选择性是一个更好的策略,它包括选择更有利的机会。
我一直等到文章的结尾来解决这个问题,但是这个模型中的一个主要缺陷,我花了几个月才意识到,就是它使用的数据是有偏见的。简而言之,一个球队的月度统计数据直接受到他们当月比赛表现的影响,所以考虑到我是用一个月的统计数据来预测球队的表现,统计数据已经包括了他们在那些比赛中的表现。这种认识让我开始建立我的新 NBA 预测模型。
未来的步骤
我选择现在撰写和发布这篇文章的原因是,我在 2020 年 1 月/2 月建立了这个模型,我一直在致力于一个更新的、更全面的模型,该模型将使用人工神经网络来预测不是赢或输,而是每次遭遇的实际分数。这种新模式将基于一个团队中的球员,而不是整个团队。我将在接下来的几周写下这段旅程,并希望分享一些好消息!
如果你喜欢这篇文章或者想讨论提到的任何信息,你可以通过 Linkedin 、 Twitter 或者通过电子邮件联系我。
从头开始构建我的神经网络
理解 Python 中深度学习的具体细节
动机
尽管已经有很多令人惊叹的深度学习框架可供使用,但我相信从头开始构建自己的神经网络有助于更好地理解神经网络的内部工作方式。
当我在 Coursera 上斯坦福机器学习课程时,有一个作业指导你完成神经网络反向传播的基本实现。所以我决定根据这个任务建立我的模型。但是有几件事我想修改/改进:
- 基于用 Octave 编写的原始赋值派生一个 Python 实现。
- 不是建立一个简单的 3 层神经网络,而是让我们自己决定神经网络架构成为可能。
- 对算法实现矢量化。
事不宜迟,我们开始吧!
创建神经网络类
基本神经网络由以下部分组成:
- 输入层
- 输出层
- 一个或多个隐藏层
- 各层之间的权重
- 激活功能
请注意:
- 输入层中的节点数与输入的要素数相同,输出的类数给出了输出层中的节点数。
- 除了输出层之外的每一层都增加了一个偏置单元。
- 有几种类型的激活功能。这里我为我的神经网络的每一层使用一个 sigmoid 函数。
所以让我们用 python 创建一个神经网络类:
模型表示
给定 m 个训练例子 ***(X,Y)***我们把它们写成矩阵:
其中, X 的每一行是 n 个特征的一个训练示例, Y 的每一行表示每个示例在 K 类中的哪一个。
我们将一个层神经网络的每一层 l 表示为如下:**
每个矩阵的第 j 行表示该层中第 j 个示例的值。这里 Sl 表示第 l 层的节点数。
每层之间的权重也用矩阵表示:
****
前馈计算
我们使用前馈计算来计算给定输入和权重的预测输出。
给定输入 X,我们首先计算第一个隐藏层的值。
首先,向输入层添加一个偏置项。然后将输入层乘以权重矩阵得到
应用激活函数以获得第一层的矩阵 A:
现在,我们只需重复此过程来计算每个图层的值,包括输出图层。
价值函数
正则化神经网络的成本函数由下式给出:
这里 a 是来自输出层的值, λ 表示正则化参数。
我们的目标是找到一个好的设置权重参数来最小化代价函数。我们可以用几个优化算法比如梯度下降来最小化函数。为了应用该算法,我们必须计算成本函数的梯度。
反向传播
我们实现反向传播算法来计算成本函数的梯度。
我写了一篇关于反向传播的实现及其工作原理的文章。如果你对反向传播的细节感兴趣,可以去看看。
这是代码:
学习参数
在我们实现了成本函数和梯度计算之后,我们使用 SciPy 的最小化函数来学习使成本函数最小化的良好参数集。
训练神经网络
让我们把所有东西放在一起训练我们的神经网络:
预言;预测;预告
现在,我们可以使用经过训练的神经网络来预测测试数据集的标签。为此,只需执行前馈计算来计算测试数据 X 的输出 Y 。
这就是了!我们已经完成了整个神经网络的构建!
感谢你和我一起踏上这个从零开始构建神经网络的奇妙旅程。我确实从整个过程中获益良多,我希望你也有同样的感受!
完整的代码可以在我的 GitHub 库中找到。欢迎给我留言!
参考
使用决斗深度 Q-学习构建用于毁灭的攻击性 AI 代理。
Pytorch 中的实现。
简介
在过去的几篇文章中,我们已经讨论了和在 VizDoom 游戏环境中实现了 深度 Q 学习 (DQN)和双重深度 Q 学习 (DDQN),并评估了它们的性能。深度 Q-learning 是一种高度灵活且响应迅速的在线学习方法,它利用场景内的快速更新来估计环境中的状态-动作(Q)值,以便最大化回报。Double Deep Q-Learning 通过将负责动作选择和 TD-target 计算的网络解耦,以最小化 Q 值高估,在训练过程的早期,当代理尚未完全探索大多数可能的状态时,这个问题尤其明显。
我们之前的双 DQN 特工在维兹杜姆环境中的表现,训练了 500 集。
本质上,使用单个状态-动作值来判断情况需要探索和学习每个单个状态的动作的效果,从而导致模型的泛化能力的固有障碍。此外,并非所有国家都同样与环境相关。
来自我们之前在 Pong 中训练的代理的预处理帧。
回想一下我们早期的实现中的 Pong 环境。在我们的代理人击球后,向左或向右移动的价值可以忽略不计,因为球必须首先移动到对手,然后返回给球员。结果,在这一点上计算用于训练的状态-动作值可能破坏我们的代理的收敛。理想情况下,我们希望能够识别每个行为的价值,而无需了解每个状态的特定效果,以便鼓励我们的代理专注于选择与环境相关的行为。
决斗深度 Q 学习(以下简称 DuelDQN)通过将 DQN 网络输出分成两个流来解决这些缺点:价值流和优势(或行动)流。通过这样做,我们部分地分离了整个国家行为评估过程。在他们的开创性论文中。al 展示了 DuelDQN 如何影响 Atari 游戏 Enduro 中代理人表现的可视化,演示了代理人如何学习专注于不同的目标。请注意,价值流已经学会关注道路的方向,而优势流已经学会关注代理人面前的直接障碍。实质上,我们通过这种方法获得了一定程度的短期和中期远见。
为了计算状态-动作的 Q 值,我们然后利用优势函数来告诉我们动作的相对重要性。平均优势的减法,计算一个状态中所有可能的动作,用于找到我们感兴趣的动作的相对优势。
根据 DuelDQN 架构的状态动作的 Q 值。
直观地说,为了获得对环境更可靠的评估,我们已经部分地分离了动作和状态值估计过程。
实现
**我们将在与上一篇文章保卫防线相同的多目标条件下,在相同的 VizDoomgym 场景中实现我们的方法。**环境的一些特征包括:
- 一个 3 的动作空间:开火,左转,右转。不允许扫射。
- 向玩家发射火球的棕色怪物,命中率为 100%。
- 试图以之字形靠近来咬玩家的粉红色怪物。
- 重生的怪物可以承受更多伤害。
- 杀死一个怪物+1 点。
- -死了得 1 分。
“防线方案”的初始状态
我们的 Google 协作实现是使用 Pytorch 用 Python 编写的,可以在 GradientCrescent Github 上找到。我们的方法基于泰伯优秀强化学习课程中详述的方法。由于我们的 DuelDQN 实现类似于我们之前的香草 DQN 实现,所以整个高级工作流是共享的,这里不再重复。
让我们从导入所有必需的包开始,包括 OpenAI 和 Vizdoomgym 环境。我们还将安装火炬视觉所需的 AV 包,我们将使用它进行可视化。请注意,安装完成后,必须重新启动运行时,才能执行笔记本的其余部分。
#Visualization cobe for running within Colab
!sudo apt-get update
!sudo apt-get install build-essential zlib1g-dev libsdl2-dev libjpeg-dev nasm tar libbz2-dev libgtk2.0-dev cmake git libfluidsynth-dev libgme-dev libopenal-dev timidity libwildmidi-dev unzip# Boost libraries
!sudo apt-get install libboost-all-dev# Lua binding dependencies
!apt-get install liblua5.1-dev
!sudo apt-get install cmake libboost-all-dev libgtk2.0-dev libsdl2-dev python-numpy git
!git clone [https://github.com/shakenes/vizdoomgym.git](https://github.com/shakenes/vizdoomgym.git)
!python3 -m pip install -e vizdoomgym/!pip install av
接下来,我们初始化我们的环境场景,检查观察空间和动作空间,并可视化我们的环境。
import gym
import vizdoomgymenv = gym.make('VizdoomDefendLine-v0')
n_outputs = env.action_space.n
print(n_outputs)observation = env.reset()import matplotlib.pyplot as pltfor i in range(22):
if i > 20:
print(observation.shape)
plt.imshow(observation)
plt.show()observation, _, _, _ = env.step(1)
接下来,我们将定义预处理包装器。这些类继承自 OpenAI gym 基类,覆盖了它们的方法和变量,以便隐式地提供所有必要的预处理。我们将开始定义一个包装器来重复许多帧的每个动作,并执行元素方式的最大值以增加任何动作的强度。您会注意到一些三级参数,如 fire_first 和no _ ops——这些是特定于环境的,在 Vizdoomgym 中对我们没有影响。
class RepeatActionAndMaxFrame(gym.Wrapper):
#input: environment, repeat
#init frame buffer as an array of zeros in shape 2 x the obs space
def __init__(self, env=None, repeat=4, clip_reward=False, no_ops=0,
fire_first=False):
super(RepeatActionAndMaxFrame, self).__init__(env)
self.repeat = repeat
self.shape = env.observation_space.low.shape
self.frame_buffer = np.zeros_like((2, self.shape))
self.clip_reward = clip_reward
self.no_ops = no_ops
self.fire_first = fire_first
def step(self, action):
t_reward = 0.0
done = False
for i in range(self.repeat):
obs, reward, done, info = self.env.step(action)
if self.clip_reward:
reward = np.clip(np.array([reward]), -1, 1)[0]
t_reward += reward
idx = i % 2
self.frame_buffer[idx] = obs
if done:
break
max_frame = np.maximum(self.frame_buffer[0], self.frame_buffer[1])
return max_frame, t_reward, done, info
def reset(self):
obs = self.env.reset()
no_ops = np.random.randint(self.no_ops)+1 if self.no_ops > 0 else 0
for _ in range(no_ops):
_, _, done, _ = self.env.step(0)
if done:
self.env.reset()
if self.fire_first:
assert self.env.unwrapped.get_action_meanings()[1] == 'FIRE'
obs, _, _, _ = self.env.step(1)
self.frame_buffer = np.zeros_like((2,self.shape))
self.frame_buffer[0] = obs
return obs
接下来,我们为我们的观察定义预处理函数。我们将使我们的环境对称,将它转换到标准化的盒子空间,将通道整数交换到张量的前面,并将其从原始(320,480)分辨率调整到(84,84)区域。我们也将我们的环境灰度化,并通过除以一个常数来归一化整个图像。
class PreprocessFrame(gym.ObservationWrapper):
#set shape by swapping channels axis
#set observation space to new shape using gym.spaces.Box (0 to 1.0)
def __init__(self, shape, env=None):
super(PreprocessFrame, self).__init__(env)
self.shape = (shape[2], shape[0], shape[1])
self.observation_space = gym.spaces.Box(low=0.0, high=1.0,
shape=self.shape, dtype=np.float32)
def observation(self, obs):
new_frame = cv2.cvtColor(obs, cv2.COLOR_RGB2GRAY)
resized_screen = cv2.resize(new_frame, self.shape[1:],
interpolation=cv2.INTER_AREA)
new_obs = np.array(resized_screen, dtype=np.uint8).reshape(self.shape)
new_obs = new_obs / 255.0
return new_obs
接下来,我们创建一个包装器来处理帧堆叠。这里的目标是通过将几个帧堆叠在一起作为单个批次,帮助从堆叠帧中捕捉运动和方向。这样,我们可以捕捉环境中元素的位置、平移、速度和加速度。通过堆叠,我们的输入采用(4,84,84,1)的形状。
class StackFrames(gym.ObservationWrapper):
#init the new obs space (gym.spaces.Box) low & high bounds as repeat of n_steps. These should have been defined for vizdooom
#Create a return a stack of observations
def __init__(self, env, repeat):
super(StackFrames, self).__init__(env)
self.observation_space = gym.spaces.Box( env.observation_space.low.repeat(repeat, axis=0),
env.observation_space.high.repeat(repeat, axis=0),
dtype=np.float32)
self.stack = collections.deque(maxlen=repeat)
def reset(self):
self.stack.clear()
observation = self.env.reset()
for _ in range(self.stack.maxlen):
self.stack.append(observation)
return np.array(self.stack).reshape(self.observation_space.low.shape)
def observation(self, observation):
self.stack.append(observation)
return np.array(self.stack).reshape(self.observation_space.low.shape)
最后,在返回最终环境供使用之前,我们将所有的包装器绑定到一个单独的 make_env() 方法中。
def make_env(env_name, shape=(84,84,1), repeat=4, clip_rewards=False,
no_ops=0, fire_first=False):
env = gym.make(env_name)
env = PreprocessFrame(shape, env)
env = RepeatActionAndMaxFrame(env, repeat, clip_rewards, no_ops, fire_first)
env = StackFrames(env, repeat)
return env
接下来,让我们定义我们的模型,一个深度 Q 网络,具有决斗架构的两个输出。这基本上是一个三层卷积网络,它采用预处理的输入观测值,将生成的展平输出馈送到一个全连接层,然后将输出分成价值流(单节点输出)和优势流(节点输出对应于环境中的动作数量)。
请注意,这里没有激活层,因为激活层的存在会导致二进制输出分布。我们的损失是我们当前状态-动作的估计 Q 值和我们预测的状态-动作值的平方差。然后,我们附上 RMSProp 优化器,以尽量减少我们在培训期间的损失。
import os
import torch as T
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as npclass DeepQNetwork(nn.Module):
def __init__(self, lr, n_actions, name, input_dims, chkpt_dir):
super(DeepQNetwork, self).__init__()
self.checkpoint_dir = chkpt_dir
self.checkpoint_file = os.path.join(self.checkpoint_dir, name) self.conv1 = nn.Conv2d(input_dims[0], 32, 8, stride=4)
self.conv2 = nn.Conv2d(32, 64, 4, stride=2)
self.conv3 = nn.Conv2d(64, 64, 3, stride=1) fc_input_dims = self.calculate_conv_output_dims(input_dims) self.fc1 = nn.Linear(fc_input_dims,1024)
self.fc2 = nn.Linear(1024, 512)
#Here we split the linear layer into the State and Advantage streams
self.V = nn.Linear(512, 1)
self.A = nn.Linear(512, n_actions) self.optimizer = optim.RMSprop(self.parameters(), lr=lr) self.loss = nn.MSELoss()
self.device = T.device('cuda:0' if T.cuda.is_available() else 'cpu')
self.to(self.device) def calculate_conv_output_dims(self, input_dims):
state = T.zeros(1, *input_dims)
dims = self.conv1(state)
dims = self.conv2(dims)
dims = self.conv3(dims)
return int(np.prod(dims.size())) def forward(self, state):
conv1 = F.relu(self.conv1(state))
conv2 = F.relu(self.conv2(conv1))
conv3 = F.relu(self.conv3(conv2))
# conv3 shape is BS x n_filters x H x W
conv_state = conv3.view(conv3.size()[0], -1)
# conv_state shape is BS x (n_filters * H * W)
flat1 = F.relu(self.fc1(conv_state))
flat2 = F.relu(self.fc2(flat1)) V = self.V(flat2)
A = self.A(flat2) return V, A def save_checkpoint(self):
print('... saving checkpoint ...')
T.save(self.state_dict(), self.checkpoint_file) def load_checkpoint(self):
print('... loading checkpoint ...')
self.load_state_dict(T.load(self.checkpoint_file))
回想一下,用于决斗深度 Q 学习的更新函数需要以下内容:
- 当前状态 s
- 当前动作一
- 当前动作后的奖励 r
- 下一个状态s’
- 下一个动作a’
为了以有意义的数量提供这些参数,我们需要按照一组参数评估我们当前的策略,并将所有变量存储在一个缓冲区中,我们将在训练期间从该缓冲区中提取迷你批次中的数据。因此,我们需要一个重放内存缓冲区来存储和提取观察值。
import numpy as np
class ReplayBuffer(object):
def __init__(self, max_size, input_shape, n_actions):
self.mem_size = max_size
self.mem_cntr = 0
self.state_memory = np.zeros((self.mem_size, *input_shape),
dtype=np.float32)
self.new_state_memory = np.zeros((self.mem_size, *input_shape),
dtype=np.float32)
self.action_memory = np.zeros(self.mem_size, dtype=np.int64)
self.reward_memory = np.zeros(self.mem_size, dtype=np.float32)
self.terminal_memory = np.zeros(self.mem_size, dtype=np.bool)
#Identify index and store the the current SARSA into batch memory def store_transition(self, state, action, reward, state_, done):
index = self.mem_cntr % self.mem_size
self.state_memory[index] = state
self.new_state_memory[index] = state_
self.action_memory[index] = action
self.reward_memory[index] = reward
self.terminal_memory[index] = done
self.mem_cntr += 1 def sample_buffer(self, batch_size):
max_mem = min(self.mem_cntr, self.mem_size)
batch = np.random.choice(max_mem, batch_size, replace=False)
states = self.state_memory[batch]
actions = self.action_memory[batch]
rewards = self.reward_memory[batch]
states_ = self.new_state_memory[batch]
terminal = self.terminal_memory[batch]
return states, actions, rewards, states_, terminal
接下来,我们将定义我们的代理,它不同于我们普通的 DQN 实现。我们的代理正在使用一个勘探率递减的ε贪婪策略,以便随着时间的推移最大化开发。为了学会预测使我们的累积奖励最大化的状态和优势值,我们的代理将使用通过抽样存储的记忆获得的贴现的未来奖励。
您会注意到,作为代理的一部分,我们初始化了 DQN 的两个副本,并使用方法将原始网络的权重参数复制到目标网络中。虽然我们的常规方法利用这种设置来生成固定的 TD-目标,但在我们的 DuelDQN 方法中,双流的存在给该过程增加了一层复杂性:
- 从重放存储器中检索状态、动作、奖励和下一状态(sar)。
- 评估网络用于生成当前状态的优势( A_s )和状态( V_s )值。
- 目标网络还用于创建下一个状态的优势( A_s_ )和状态( V_s_ )值。
- 预测的 Q 值是通过将当前状态的优势和状态值相加,并减去用于归一化的当前状态优势值的平均值而生成的。
- 通过将下一个状态的优势和状态值相加,并减去下一个状态优势值的平均值以进行归一化,来计算目标 Q 值当前状态。
- 然后,通过将折扣后的目标 Q 值与当前状态奖励相结合,构建 TD 目标。
- 通过将 TD 目标与预测的 Q 值进行比较来计算损失函数,然后将其用于训练网络。
import numpy as np
import torch as T
#from deep_q_network import DeepQNetwork
#from replay_memory import ReplayBufferclass DuelDQNAgent(object):
def __init__(self, gamma, epsilon, lr, n_actions, input_dims,
mem_size, batch_size, eps_min=0.01, eps_dec=5e-7,
replace=1000, algo=None, env_name=None, chkpt_dir='tmp/dqn'):
self.gamma = gamma
self.epsilon = epsilon
self.lr = lr
self.n_actions = n_actions
self.input_dims = input_dims
self.batch_size = batch_size
self.eps_min = eps_min
self.eps_dec = eps_dec
self.replace_target_cnt = replace
self.algo = algo
self.env_name = env_name
self.chkpt_dir = chkpt_dir
self.action_space = [i for i in range(n_actions)]
self.learn_step_counter = 0 self.memory = ReplayBuffer(mem_size, input_dims, n_actions) self.q_eval = DeepQNetwork(self.lr, self.n_actions,
input_dims=self.input_dims,
name=self.env_name+'_'+self.algo+'_q_eval',
chkpt_dir=self.chkpt_dir) self.q_next = DeepQNetwork(self.lr, self.n_actions,
input_dims=self.input_dims,
name=self.env_name+'_'+self.algo+'_q_next',
chkpt_dir=self.chkpt_dir)#Epsilon greedy action selection
def choose_action(self, observation):
if np.random.random() > self.epsilon:
# Add dimension to observation to match input_dims x batch_size by placing in list, then converting to tensor
state = T.tensor([observation],dtype=T.float).to(self.q_eval.device)
#As our forward function now has both state and advantage, fetch latter for actio selection
_, advantage = self.q_eval.forward(state)
action = T.argmax(advantage).item()
else:
action = np.random.choice(self.action_space) return action def store_transition(self, state, action, reward, state_, done):
self.memory.store_transition(state, action, reward, state_, done) def sample_memory(self):
state, action, reward, new_state, done = \
self.memory.sample_buffer(self.batch_size) states = T.tensor(state).to(self.q_eval.device)
rewards = T.tensor(reward).to(self.q_eval.device)
dones = T.tensor(done).to(self.q_eval.device)
actions = T.tensor(action).to(self.q_eval.device)
states_ = T.tensor(new_state).to(self.q_eval.device) return states, actions, rewards, states_, dones def replace_target_network(self):
if self.learn_step_counter % self.replace_target_cnt == 0:
self.q_next.load_state_dict(self.q_eval.state_dict()) def decrement_epsilon(self):
self.epsilon = self.epsilon - self.eps_dec \
if self.epsilon > self.eps_min else self.eps_min def save_models(self):
self.q_eval.save_checkpoint()
self.q_next.save_checkpoint() def load_models(self):
self.q_eval.load_checkpoint()
self.q_next.load_checkpoint()
def learn(self):
if self.memory.mem_cntr < self.batch_size:
return self.q_eval.optimizer.zero_grad() #Replace target network if appropriate
self.replace_target_network() states, actions, rewards, states_, dones = self.sample_memory()
#Fetch states and advantage actions for current state using eval network
#Also fetch the same for next state using target network
V_s, A_s = self.q_eval.forward(states)
V_s_, A_s_ = self.q_next.forward(states_) #Indices for matrix multiplication
indices = np.arange(self.batch_size) #Calculate current state Q-values and next state max Q-value by aggregation, subtracting constant advantage mean
q_pred = T.add(V_s,
(A_s - A_s.mean(dim=1, keepdim=True)))[indices, actions]
q_next = T.add(V_s_,
(A_s_ - A_s_.mean(dim=1, keepdim=True))).max(dim=1)[0] q_next[dones] = 0.0
#Build your target using the current state reward and q_next
q_target = rewards + self.gamma*q_next loss = self.q_eval.loss(q_target, q_pred).to(self.q_eval.device)
loss.backward()
self.q_eval.optimizer.step()
self.learn_step_counter += 1 self.decrement_epsilon()
定义了所有支持代码后,让我们运行主训练循环。我们已经在最初的总结中定义了大部分,但是让我们为后代回忆一下。
- 对于训练集的每一步,在使用ε-贪婪策略选择下一个动作之前,我们将输入图像堆栈输入到我们的网络中,以生成可用动作的概率分布
- 然后,我们将它输入到网络中,获取下一个状态和相应奖励的信息,并将其存储到我们的缓冲区中。我们更新我们的堆栈,并通过一些预定义的步骤重复这一过程。
- 在一集的结尾,我们将下一个状态输入到我们的网络中,以便获得下一个动作。我们还通过对当前奖励进行贴现来计算下一个奖励。
- 我们通过上面提到的 Q 学习更新函数生成我们的目标 y 值,并训练我们的网络。
- 通过最小化训练损失,我们更新网络权重参数,以便为下一个策略输出改进的状态-动作值。
- 我们通过跟踪模型的平均得分(在 100 个训练步骤中测量)来评估模型。
env = make_env('VizdoomDefendLine-v0')
best_score = -np.inf
load_checkpoint = False
n_games = 2000
agent = DuelDQNAgent(gamma=0.99, epsilon=1.0, lr=0.0001,input_dims=(env.observation_space.shape),n_actions=env.action_space.n, mem_size=5000, eps_min=0.1,batch_size=32, replace=1000, eps_dec=1e-5,chkpt_dir='/content/', algo='DuelDQNAgent',env_name='vizdoogym')if load_checkpoint:
agent.load_models()fname = agent.algo + '_' + agent.env_name + '_lr' + str(agent.lr) +'_'+ str(n_games) + 'games'
figure_file = 'plots/' + fname + '.png'n_steps = 0
scores, eps_history, steps_array = [], [], []for i in range(n_games):
done = False
observation = env.reset() score = 0
while not done:
action = agent.choose_action(observation)
observation_, reward, done, info = env.step(action)
score += reward if not load_checkpoint:
agent.store_transition(observation, action,reward, observation_, int(done))
agent.learn()
observation = observation_
n_steps += 1scores.append(score)
steps_array.append(n_steps)avg_score = np.mean(scores[-100:])if avg_score > best_score:
best_score = avg_score
print('Checkpoint saved at episode ', i)
agent.save_models()print('Episode: ', i,'Score: ', score,' Average score: %.2f' % avg_score, 'Best average: %.2f' % best_score,'Epsilon: %.2f' % agent.epsilon, 'Steps:', n_steps)eps_history.append(agent.epsilon)
if load_checkpoint and n_steps >= 18000:
break
我们绘制了 500 集、1000 集和 2000 集的代理商平均分和我们的 epsilon 值。
500 集后我们经纪人的奖励分配。
1000 集后我们经纪人的奖励分配。
2000 集后我们经纪人的报酬分配。
查看结果并将其与我们的普通 DQN 实施和双 DQN 实施进行比较,您会注意到在 500、1000 和 2000 集的分布中有显著提高的改善率。此外,具有更受约束的报酬振荡,表明当比较任一实现时改进了收敛。
我们可以想象我们的代理人在 500 和 1000 集以下的表现。
500 集的代理性能。
在 500 集时,代理已经采用了先前在较高训练时间为 DQN 和 DDQN 识别的相同策略,归因于在局部最小值的收敛。仍然会采取一些攻击性的行动,但是主要的策略仍然依赖于怪物之间的友好射击。
at 1000 集呢?
1000 集的代理性能。
我们的经纪人已经成功地打破了本地化的最低限度,并发现了一个更具进攻性的角色为导向的替代战略。这是我们的 DQN 和 DDQN 模型都无法做到的,即使是在 2000 集的情况下——证明了 DuelDQN 的双流方法在识别和优先考虑与环境相关的行动方面的效用。
这就结束了双深度 Q 学习的实现。在我们的下一篇文章中,我们将通过把我们所学的所有知识结合到一个方法中来完成我们的 Q 学习方法系列,并在一个更动态的结局中使用它。
我们希望你喜欢这篇文章,并希望你查看 GradientCrescent 上的许多其他文章,涵盖人工智能的应用和理论方面。为了保持对 GradientCrescent 的最新更新,请考虑关注该出版物并关注我们的 Github 资源库
来源
萨顿等人。al,“强化学习”
塔博尔,“运动中的强化学习”
西蒙尼尼,“深度 Q 学习的改进*
用 Python 构建人口模型
概念模型和动画视觉效果,用于在没有大量数据的情况下讲述强大的数据科学故事。
做“数据科学”的一大挑战是收集足够的数据。许多模型,尤其是机器视觉或自然语言处理等复杂问题的模型,需要大型数据集,尽管我们可能生活在大数据时代,但在家中利用大型高质量数据集并不总是容易的。特别是对于数据科学的学生或该领域的新手来说,缺乏整洁的数据似乎会成为学习的障碍——我们还能从 iris 数据集或泰坦尼克号数据集获得多少里程?
如果你是一名数据科学家或正在接受培训的数据科学家,正在寻找一些兼职项目,我想建议有另一种方法来练习高度相关的技能,这种方法不需要大量的网络搜集或对精选数据的高级访问。如果说有什么不同的话,那就是许多数据科学教学(特别是在越来越普遍的训练营)对高精度预测的关注——这种预测需要基于大量数据训练的模型——不利于建立模型的基本用途之一:创建概念框架以更好地理解事物如何或为什么以它们的方式工作。考虑到这一点,我认为不需要收集数据就可以建立有用的模型。
在新冠肺炎疫情的世界里,这种模型无论是作为具体预测的基础,还是作为公共交流的工具,都显示出了极大的价值。也许你已经读过《华盛顿邮报》的那篇文章,这篇文章基本上像病毒一样传播开来,并迅速成为他们有史以来最受欢迎的在线文章。或者看了 3Blue1Brown 的优秀视频和他的各种疫情场景的模型。在这两种情况下,以及许多其他情况下,非常有用的说明性视觉效果已经建立在不需要先前数据的模型之上。当然,这些模型的目标略有不同;他们并不试图预测确切的病例数或诸如此类的事情,相反,他们的目标是建立一种直觉,了解某些特征可能如何影响结果。但是,这仍然是有价值的,对于一种没有历史数据的新疾病,这可能是你能做的最好的了。
值得强调的是,从技术的角度来看,华盛顿邮报中强调的模型和视觉效果并不特别难构建。大多数初级数据科学家对 Python 和常用数学和绘图库有基本的掌握,他们应该有技术技能来构建这样的模型,并创建动画或交互式视觉效果。考虑到这一点,这里有几个开发人口模型的小项目,并为任何有兴趣在这方面创造自己的视觉效果的人提供相应的视觉效果。
疾病在人群中传播的简单模拟
任何类型的建模规则一:做好你的研究,不要太相信结果!
这确实适用于任何项目,但是如果你的模型不是建立在经验观察的基础上,它的重要性就会被放大。如今,人们通常根据预测模型的表现来判断它们,而不是根据它们对为什么做出某些预测的说法来判断——深度神经网络甚至以的不透明而闻名——但我们将讨论的各种模型没有验证集。相反,它们是一系列已经被操作化的假设,所以你可以看到它们是如何发挥作用的。你能做出什么样的假设或简化,并且仍然能看到有用的结果?这在很大程度上取决于您正在建模的内容,而判断什么样的模型有意义的唯一方法是首先积累一些领域知识。
这类模型通常也不会给你准确的预测,也不应该这样对待。重要的是要明白,我们的目标不一定是预测未来,而是感受某些事情是如何影响潜在结果的。在我描述建立一个流行病模型之前,让我声明我不是流行病学家,也不是我提到的其他新冠肺炎模型的创造者。我们创建的任何模型都不应被理解为对疾病将如何传播的准确预测。相反,它们可以说明现实世界中可能存在的联系和影响,或者某些影响的大小。有一句话,通常被认为是乔治·博克斯说的,所有的模型都是错的,但有些是有用的。我们的目标是完全属于错误但有用的类别。
项目 1:使用 Lotka-Volterra 的简单人口模型
洛特卡-沃尔泰拉方程是一组简单的微分方程,也称为捕食者-猎物方程,你可能在高中生物课上遇到过。名副其实,他们模拟了捕食者和被捕食者相互作用的种群动态,其中每个种群的增长或下降部分取决于当前的种群规模。一个地区的大量野兔会让以野兔为食的狐狸数量增长,但随着狐狸数量的增长,捕食可能会开始减少野兔的数量,进而给狐狸的数量带来压力,等等。
出于我们的目的,Lotka-Volterra 方程是一个很好的起点,因为它们易于实现。只有两个方程,一个描述被捕食者种群的变化率,另一个描述捕食者种群的变化率。被捕食种群的公式为:
其中 x 是当前的猎物种群,y 是当前的捕食者种群,α是猎物的生长常数,β是代表捕食率的常数。猎物数量的相应等式是:
这两个方程合在一起形成了周期曲线,在捕食者数量少的时期,被捕食者数量迅速增长,但随着捕食者数量的增长,被捕食者数量又会下降。如果解微分方程的想法让你望而生畏,请认识到,你可以通过对每个变化率进行瞬时估计,向前增加一点时间,然后计算新的变化率,而无需任何复杂的数学运算,这就是我在这里所做的:
一个简单的人口模型
虽然这不是一个没有吸引力的图表,但我们希望真正利用视觉的力量,我们可以通过两种方式做到这一点。一种方法是将图形制成动画,就像实时描绘这些曲线一样。最普遍的 Python 图形库 Matplotlib 实际上有一个专门用于创建动画的模块。该模块只有两种方法。一种方法是通过收集一系列 Matplotlib artist 对象来创建动画(比如 Axes 或 Line2d 对象,它们代表您正在绘制的事物的位置或绘制方式)。另一个应用您提供的函数来重复更新图形。该模块非常强大,但是可能有点敏感,因此有必要详细地浏览一个示例。
对于这种情况,我将使用。FuncAnimation()函数,它将重复调用一个函数来更新图形的信息。原则上,您可以使用这个函数来不断更新您的底层模型,如果您想要放弃一个没有设定结尾的动画,并看着它无限期地继续播放,这将是有意义的。如果您对动画有一个固定的结尾,我通常发现更简单的方法是运行模型,无论您需要创建多长时间的数据,然后使用动画功能来引用这些数据的增加部分。需要记住的另一件事是 Matplotlib 会自己显示动画,它没有办法将它们保存为视频文件或 gif 文件。你需要一个外部包来完成这个任务。我用 ImageMagick 制作我的 gif。实现 Lotke-Volterra 方程动画的完整代码块如下:
以下是这段代码创建的内容:
Matplotlib 动画
模型是确定性的;对于四个参数的任何给定值,以及两个总体的任何给定值,两个总体的大小只会以一种方式出现。要考虑的问题是,当参数改变时,这条路径将如何运行。当捕食率增加或减少时,图表会有怎样的变化?什么因素会改变周期的长短?
为了真正探索模型中参数的相互作用,我们可以转向第二个技巧,即增加交互性。如果能够为每个参数设置新值,并让可视化自动重新计算,而不需要更改我们的代码,这将是很有帮助的。为此,我们可以考虑用 Dash 之类的工具构建一个仪表板(Matplotlib 也有图形交互工具,但 Dash 是一个常用工具,因此使用起来会更简单)。让我们建立一个简单的仪表板,它有四个滑块来控制参数的值。
在 Dash 中设置仪表板的代码分为两部分。在第一部分中,我们定义了仪表板的布局,使用 Dash 的 html 组件模块来控制仪表板的 html 布局。在第二种情况下,一系列回调函数将您拥有的任何输入(如本例中的滑块)连接到图形,并定义图形如何对输入做出反应。
这个示例 dash 非常简单,毕竟没有样式,只有仪表板的核心组件,但是能够实时处理参数和查看结果,这提供了一个有启发性的视觉效果,让您可以真正了解等式是如何工作的。这个方程有一个有趣的特征,表面上并不明显,那就是降低α,即猎物物种的增长率,实际上并不会降低猎物物种达到的峰值,因为这些峰值是由捕食者和猎物物种之间的相互作用决定的,较低的增长率最终也会降低捕食者物种的数量:
降低 alpha 值会改变图形的周期,但不会改变被捕食物种的峰值
当然,有一个重要的问题是,这个模型是否真的描述了野生动物的数量是如何随着时间变化的。为了回答这个问题,我们需要阅读生物学,但是我要重申的是,这个模型不需要准确地预测实际的动物数量才是有用的。
项目 2:流行病学建模
从静态的两方程模型到华盛顿邮报或 3Blue1Brown 视频中强调的那种更动态的半随机模型,需要更多的编码,但比你想象的要少。他们强调的那种先生流行病学模型有更多的活动部分,大量的个体被建模出来,一定量的随机机会发挥作用,但是,计算机做了大部分的繁重工作。真正的工作是考虑如何建立模型,而不是实际建模。
这种模型是面向对象编程的主要用例。毕竟,该模型实际上只是代表了一群具有特定属性的个体(他们是否被感染,他们的位置,他们被感染了多长时间,等等)。)和某些相互作用的方式(从一个地方移动到另一个地方,传播疾病)。因此,我们可以通过创建一个表示单个案例的对象类并让它们进行交互来模拟模型的工作。
对于这个模型,我们的类需要一些属性。当然,我们需要知道个人是否生病,因此一个属性是“状态”,我将它设置为 0 表示未感染但易感染,1 表示生病,2 表示已移除。每个人还需要一个位置,以及一些关于他们如何移动的信息,所以我也给了他们一个“速度”。我还想象每个人都有一套“界限”,标明他们活动的大致区域。你可以用这个来建模,比如说,生活在不同城市的人(就像 3Blue1Brown 所做的那样)或者不同的社区,他们之间有一些联系,但不是完全重叠。
最后,你需要描述个人可能改变和互动的方式。在这个简单的版本中,我包含了一个设置个人运动方向的方法,一个沿着运动方向改变他们位置的方法和一个覆盖可能传播的方法。在这个模型中,我假设离一个有传染性的人足够近(我们的模型对社会互动的粗略代表)会带来一定的患病概率。在这个例子中,半径和概率是全局变量,所以我为每个人都设置了相同的值,但是您可以很容易地更改代码,使它们成为个体本身的属性,并为不同的个体设置不同的值(也许有些个体天生就不容易受到影响)。在这个简单的示例中,您可以像这样设置该类:
现在您已经设置了这个类,实际上运行这个模型是非常简单的。生成一堆对象并将它们随机放置在整个网格中,其中一些应该用设置为 1 的“状态”进行实例化,表示已感染。在每一轮,更新他们的位置,看看是否有任何传播发生,然后跟踪有多少人被感染。冲洗并重复,记录每个回合中每个类别有多少人,然后根据数据生成您的绘图,这次使用 ArtistAnimation()函数,该函数接受 Matplotlib artist 对象的列表:
这就产生了这个情节,很像《华盛顿邮报》文章中的情节:
我们的简单流行病学模型的图形结果,蓝色表示易感,红色表示受感染,灰色表示已移除
创建动画散点图遵循基本相同的步骤,除了他们的状态之外,我们还需要跟踪每个人的位置。为了使视觉更容易理解,我还减少了创建的个体数量并减慢了它们的速度(也减少了感染半径和感染概率,因为当你减慢个体速度时,它们在慢慢经过时会在彼此附近转更多的圈,这实质上提高了你的感染率)。
它将会创造出类似这样的东西,这取决于机遇:
这些图与 Grant Sanderson 在 3Blue1Brown 制作的图有两个主要区别。一个是样式的质量——我用尽可能少的样式展示了这些示例,以尽可能清晰地展示代码的机制,而 3Blue1Brown 动画经过了仔细而美丽的渲染,以最大限度地清晰最终的动画。格兰特制作的动画可能超出了我的能力——他实际上编写了自己的动画库来制作他的视频——但是使用 Matplotlib 的工具来制作一些优秀的动画是可能的。
第二个大的区别实际上与模型或代码无关,而是所提问题的信息量。不过,我要再次强调,任何有好奇心并愿意做一些研究的人都可以开始问一些有趣的问题。你如何让这个模型更好地符合我们对这种疾病的了解?这些模型中个体行为的变化会如何影响结果?寻找答案的障碍比你想象的要低。
用 MyAnimeList 和 Sklearn 构建预测模型(第 1 部分)
本文使用 MyAnimeList 数据库中的许多特性变量来预测用户评分
MyAnimeList is one of the largest online data repositories for anime on the internet with listings ranging from TV series to Manga comics and data dating back to ~1905 (for anyone interested, this is 活動写真/Katsudou Shashin). Luckily this data is all available on Kaggleand comes split into different components/data-frames: user ratings and anime listing information. Given my mutual love for anime and all things data, I thought it would be cool to combine data and to build some predictive models and basic recommendation systems in Python. I will discuss the intricacies of such in more detail below. The first section of my work with the MyAnimeList will focus on some machine learning techniques used to predict user rating scores using a variety of features that were computed for analysis using Sklearn. The second section will follow this post and will hone in on some simple techniques used to recommend anime content, based on both user rating correlations and feature variables.
数据准备
在执行任何分析之前,安装必要的软件包:
import numpy as np
import pandas as pd
import sklearn
import matplotlib.pyplot as plt
import seaborn as sb
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import accuracy_score
from sklearn.neural_network import MLPRegressor, MLPClassifier
from sklearn import metrics
from matplotlib import rcParams
import joblib
from sklearn.tree import export_graphviz
import pydot
from IPython.display import Image
从本地导入两个数据框:
local_1 = '/Users/hopkif05/Desktop/rating.csv'
ratings = pd.read_csv(local_1)local_2 = '/Users/hopkif05/Desktop/AnimeList.csv'
anime_list = pd.read_csv(local_2)
并将它们连接在一个唯一的标识符上,在这种情况下是它们的 anime_id :
anime = pd.merge(ratings, anime_list, on=’anime_id’)
anime.head()
您可以从上面的数据框中看到,我们现在在数据库中有每个用户的评级,这些评级已经与该内容相关的所有元数据合并在一起。这里有大约 800 万个用户评级,有 33 行,每一行代表我们模型的一个潜在特征变量。为了后续的分析,我已经选择了 user_id、anime_id、rating_x、title_english、type、source、scored_by、score、favorites、members、popularity 和 studio 作为模型中包含的变量;并将它们存储在一个新的数据帧中:
anime_df = anime[[‘user_id’,’anime_id’,’rating_x’,’title_english’,’type’,’source’,’scored_by’,’score’,’favorites’,’members’,’popularity’,’studio’]].copy()
anime_df.head(100)
您可能会注意到,一些变量是分类变量,并且目前没有正确的格式来包含在任何后续的建模中。为了准备这些特性,我们可以对数据中的某些列进行一次性编码。我将使用我们的源特性作为例子:
enconder = LabelEncoder()
source_labels = enconder.fit_transform(anime_df[‘source’])
source_mappings = {index: label for index, label in
enumerate(enconder.classes_)}
source_mappings>>>>>> {0: '4-koma manga',
1: 'Book',
2: 'Card game',
3: 'Digital manga',
4: 'Game',
5: 'Light novel',
6: 'Manga',
7: 'Music',
8: 'Novel',
9: 'Original',
10: 'Other',
11: 'Picture book',
12: 'Radio',
13: 'Unknown',
14: 'Visual novel',
15: 'Web manga'}
正如您从我们的源变量中看到的,每个源类型都被分配了一个数字变量,可用作我们预测模型的特征变量。对您希望在建模中使用的变量重复此操作,并将它们合并回您的原始数据框:
anime_df[‘source_label’] = source_labels
anime_df[‘type_label’] = type_labels
anime_df[‘title_label’] = title_labels
数据建模
现在我们已经准备好了我们的特征变量,我们准备开始数据建模。在此之前,重要的是在我们的数据框架中可视化变量之间的关系。因为我们希望预测用户评级,所以我们希望了解我们的变量与 rating_x: 的相关程度
plt.figure(figsize=(12,10))
cor = anime_df.corr()
sb.heatmap(cor, annot=True, cmap=plt.cm.Dark2)
plt.show()
查看我们数据中总体分数的分布也很有用:
sb.distplot(anime_df[‘score’], color=”orchid”)
由于我们的结果变量( rating_x )与计算出的任何特征变量几乎没有相关性,因此我创建了一个二元结果测量,它使用平均用户评级分数作为高于或低于平均分数的阈值:
anime_df.rating_x.mean()
anime_df[‘rating_bracket’] = np.where(anime_df[‘rating_x’] >= 6.14, ‘1’, ‘0’)
现在,我们希望将这些数据分成训练、测试和验证部分,相应地将结果度量和特征变量分开。由于我们当前的框架中有大约 800 万行数据,运行模型可能会很耗时;为此,我从 anime_df 数据帧中随机抽取了 250k 的样本:
## Take a random sampleanime_sample = anime_df.sample(n=250000, random_state=1)features = anime_sample[[‘favorites’,’members’, ‘popularity’,’scored_by’,’source_label’, ‘type_label’,’title_label’]].copy()
labels = anime_sample[‘rating_bracket’]## Train — testX_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.4, random_state=42)## Validation
X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, test_size=0.5, random_state=42)
随机森林模型
我们要训练的第一个模型是随机森林;你可以在我之前写的 的 博客中读到更多关于训练一个随机森林模型的内容。在运行我们的随机森林模型之前,我们将首先优化两个输入参数,它们是 n_estimators 和 max_depth ,它们分别代表决策树的数量和每棵树的深度:
def print_results(results):
print(‘BEST PARAMS: {}\n’.format(results.best_params_))means = results.cv_results_[‘mean_test_score’]
stds = results.cv_results_[‘std_test_score’]
for mean, std, params in zip(means, stds, results.cv_results_[‘params’]):
print(‘{} (+/-{}) for {}’.format(round(mean, 3), round(std * 2, 3), params))rf = RandomForestClassifier()
parameters = {
‘n_estimators’: [50,100],
‘max_depth’: [10,20,None]
}rf_cv = GridSearchCV(rf, parameters, cv=5)
rf_cv.fit(X_train, y_train.values.ravel())print_results(rf_cv)
我们可以将最佳参数设置存储到本地机器,以便在评估结束时用于模型验证:
joblib.dump(rf_cv.best_estimator_, ‘/.../.../.../AnimeRecs/RF_model.pkl’)
现在,我们可以使用优化的参数运行随机森林,并打印模型的准确性:
rf_model = RandomForestClassifier(n_estimators=100, max_depth=20)
rf_model.fit(X_train, y_train)
rf_predicted_values = rf_model.predict(X_test)
score = accuracy_score(y_test,rf_predicted_values)
print(score)>>>> Accuracy: 0.695905
您可以看到,我们的随机森林模型达到了大约 70%的准确率,这表明我们可以使用我们为此分析创建的特征变量以相当高的准确率预测用户评分。在得出这个结论之前,评估哪些变量与模型成功相关是很重要的。我们可以这样看待输入变量的相对重要性:
for name, importance in zip(features.columns, rf_model.feature_importances_):
… print(name, “=”, importance)favorites = 0.2778530464552122
members = 0.19784680037872093
popularity = 0.1759396781482536
scored_by = 0.15560656547263593
source_label = 0.0509643924057434
type_label = 0.04176822827070301
title_label = 0.10002128886873099
以上变量按降序排列,最重要的特性在顶部。您可以看到,我们编码的变量对我们的模型产生的重要性很小,并且在这种情况下可能具有最小的预测能力。就对我们的模型的相对重要性而言,最相关的特征变量是动画内容有多少个收藏夹,这完全是直观的。
我们还可以在随机森林模型中可视化一个单独的决策树,以查看数据是如何传递的:
export_graphviz(tree,
feature_names=features.columns,
out_file=’rf_anime_tree.dot’,
filled=True,
rounded=True)
前馈多层感知器
我们要训练的第二个模型是多层感知器( MLP ),这是一类前馈神经网络,旨在模拟大脑处理和存储信息的神经生理过程。MLP 通常用于监督学习问题,它们在一组输入-输出对上进行训练,并学习对它们之间的相关性进行建模。想了解更多关于 MLP 的信息,请阅读我之前的一篇文章。
我们将为我们的 MLP 模型优化的超参数是 hidden_layer_sizes ,这是第 I 个隐藏层中的节点数,以及激活、,这是隐藏层的激活函数。对于激活功能,我们将确定逻辑激活和 relu 激活之间哪个功能更好:
逻辑:使用 sigmoid 函数(如逻辑回归),返回 f(x) = 1 / (1 + exp(-x))
Relu :整流后的线性单位函数,返回 f(x) = max(0,x)。如果 value 值为正,该函数输出输入值,否则传递一个零
mlp = MLPClassifier()
parameters = {
‘hidden_layer_sizes’: [(10,), (50,)],
‘activation’: [‘relu’, ‘logistic’]
}
mlp_cv = GridSearchCV(mlp, parameters, cv=5)
mlp_cv.fit(X_train, y_train.values.ravel())
print_results(mlp_cv)
我们可以将最佳参数设置存储到本地机器,以便在评估结束时用于模型验证:
joblib.dump(rf_cv.best_estimator_, ‘/.../.../.../AnimeRecs/MLP_model.pkl’)
现在,我们可以使用优化的参数运行我们的 MLP 模型,并打印我们模型的精确度:
rf_model = RandomForestClassifier(n_estimators=100, max_depth=20)
rf_model.fit(X_train, y_train)
rf_predicted_values = rf_model.predict(X_test)
score = accuracy_score(y_test,rf_predicted_values)
print(score)>>>> Accuracy: 0.6758805
如您所见,该模型达到了大约 68%的准确率,低于我们的随机森林模型。由于 MLP 模型循环遍历数据的方式,在不同的时期/遍数遍历整个训练数据集之后评估模型的性能非常重要。为了评估这一点,我们可以设想模型随时间的验证损失:
loss_values = mlp_model.loss_curve_
mlp_model.score
plt.plot(loss_values)
plt.ylim((0.61,0.640))
plt.axvline(10,0,0.7)
plt.show()
从上图可以看出,10 个时期后,验证损失开始增加,这表明模型过度拟合。减少通过我们的训练数据的完整次数可能是有价值的,从而减少训练我们的模型的时间。然而,值得考虑的是,这可能会降低模型的整体准确性。
模型验证
如前所述,我们对数据进行了分割,这样我们就有了一个验证集,用于在评估的最后阶段对模型进行比较。这些数据对于所使用的两个模型中的任何一个都是完全看不到的,因此可以用作我们训练的模型的性能的强有力的衡量标准。此外,我们还存储了为此目的使用各种超参数的最佳估计值。
以下代码将遍历您存储的最佳估计值:
models = {}
for mdl in [‘MLP’, ‘RF’]:
models[mdl] = joblib.load(‘/Users/hopkif05/Desktop/AnimeRecs/{}_model.pkl’.format(mdl))
models
下面的代码将创建一个函数来评估和比较所使用的两个模型的准确性。正如所见, model.predict() 函数存在于开始和结束时间函数之间,这意味着我们可以计算每个模型的延迟值,以评估它们计算预测需要多长时间:
def evaluate_model(name, model, features, labels):
start = time()
pred = model.predict(features)
end = time()
accuracy = round(accuracy_score(labels, pred), 3)
print('{} -- Accuracy: {} / Latency: {}ms'.format(name,
accuracy,
round((end - start)*1000, 1)))
我们现在可以遍历我们的模型来确定它们的准确性:
for name, mdl in models.items():
evaluate_model(name, mdl, X_val, y_val)MLP -- Accuracy: 0.683 / Latency: 675.7ms
RF -- Accuracy: 0.713 / Latency: 668.9ms
如上所述,随机森林模型在看不见的验证数据上表现最好。有趣的是,它还具有更短的延迟,这表明 MLP 模型已经被训练了太长时间;如前所述,这会对训练神经网络产生负面影响。
因此,可以总结为,我们的随机森林模型可以使用各种特征变量来预测 MyAnimeList 上的用户评级。考虑到随机森林模型在分类问题上表现良好,如我们的动漫数据中使用的二元结果测量,这一结果并不十分令人惊讶。给定我们为我们的随机森林输出计算的特征重要性,可以进一步确定与我们的随机森林模型的准确性最相关的变量是:收藏夹 (0.28)、成员 (0.20)、流行度 (0.18)和由 (0.16)评分。这些发现得到了上面打印的相关矩阵的支持,考虑到这些指标的性质,这些发现并不令人惊讶;因此,与我们的一次性编码变量相比,预计它们会产生相对较高的预测能力。
构建生产就绪的机器学习模型
了解具有本机 MLOps 的全新横向扩展 RDBMS,它可以轻松开发、管理、部署和治理 ML 模型。
资料来源:Peshkova/Adobe
作者:蒙特·兹韦本和本·爱泼斯坦
随着今天开发的机器学习包的质量,测试和创建模型再容易不过了。数据科学家可以简单地导入他们最喜欢的库,并立即访问数十种前沿算法。但是,创建生产就绪的机器学习模型需要的不仅仅是有效的算法。这需要实验、数据探索和坚实的组织。
数据科学家需要一个环境来自由探索数据,绘制不同的趋势和相关性,而不受数据集大小的限制。借助 Splice Machine 的 MLManager 2.0 平台,构建、测试、实验和将机器学习模型无缝部署到生产中再容易不过了。
以下是 ML Manager 最新版本中提供的数据科学功能的总结。
Jupyter 笔记本
作为 ML Manager 2.0 的一部分,我们已经将 Jupyter 笔记本电脑直接构建到具有 BeakerX 支持的拼接机器平台中。Jupyter 笔记本是数据科学家工作和共享数据以及代码的最受欢迎的工具。它们易于使用,并允许通过将数据科学过程的各个部分分成不同的单元来模块化工作流。
用 Jupyter 处理熊猫数据帧——来源:拼接机
懂得多种语言的
在 ML Manager 2.0 中,我们不仅仅提供对通用 Jupyter 笔记本的访问。有了 ML Manager 2.0,数据科学家拥有了定制的笔记本,并增加了 BeakerX 的功能。我们已经与开源 BeakerX 项目合作,并为 Splice Machine 定制了它。这种定制的一个最重要的特性是多语言编程。这种编程范式允许数据科学家在同一个笔记本中用多种不同的语言编写代码。每个单元格都可以用“魔法”来定义,告诉 Jupyter 如何解释代码。
查看拼接机器上 BeakerX 内置的可用魔法命令—来源:拼接机器
数据科学家可以灵活地使用%%定义整个单元格的语言,或者使用%定义单行的语言。这允许你在同一个笔记本上写 SQL 代码,比如 Python、Scala、Java 或 R 代码。
在同一个 Jupyter 笔记本上运行不同语言的多个单元——来源:拼接机
在分离的多语言编程之上,我们还允许交叉内核变量共享。这意味着您可以用一种语言定义一个变量,并使用底层的 BeakerX 变量与另一种语言共享它。这使得数据科学家能够前所未有地访问他们的 SQL 数据。例如:
在 SQL 和 Python 之间直接共享变量—来源:Splice Machine
BeakerX 还有许多其他的优秀特性,这些特性直接内置在 Splice Machine 中。
形象化
Jupyter 笔记本电脑还配有出色的内置可视化工具,例如用于 2D 和 3D 绘图的 matplotlib 和 plotly:
在 Python 单元中查看 plotly 3D 图形-来源:Splice Machine
数据科学家还可以访问交互式 Pandas 表,您可以在其中对结果进行过滤和排序,并直接向表中添加可视化效果:
将结果直接过滤和分类到 Pandas 表中——来源:拼接机
原生 Spark 数据源— PySpliceContext
数据科学中的一个严重问题是过度拟合。过度拟合可能由许多不同的因素造成。这些问题包括创建的模型对训练数据过于精细,在数据集中进行有偏见的分割,甚至为问题选择错误的算法。然而,所有这些问题都可以克服。
数据科学团队面临的另一个挑战是数据规模问题。最难克服的偏差形式是倾斜数据集。许多数据科学团队遇到了在数据子集上构建模型,然后在整个数据集上训练模型的问题。这有可能给模型带来巨大的偏差。
因此,根据全部可用数据训练模型至关重要,而有了拼接机,这不再是一个问题。因为我们的 Jupyter 笔记本电脑与我们的数据库部署在同一位置,所以数据科学家可以直接使用横向扩展功能,即时访问他们的全部数据集。更重要的是,数据工程师和数据科学家使用与应用程序开发人员不同的编程模式。他们操纵火花和熊猫数据帧。现在 Splice 也提供了这种模式和完整的 CRUD API。使用我们的原生 Spark 数据源,您可以查询整个表,无论大小如何,并立即将其作为 Spark 数据帧使用。在几毫秒内,只需一次 api 调用,您就可以将表中的所有数据移动到 Spark Dataframe 中,并在屏幕上显示出来。更强大的是能够将数据帧插入到 Splice 中,并具有完全的 ACID 事务性。这种级别的数据访问和操作使得模型构建和数据探索比以往任何时候都更容易。
想象一下,能够以事务方式更新实时特征库,供多个模型实时使用。例如,这些特征存储可以有数万列跟踪客户行为模式的数据,这些数据可以立即转化为数据帧中的特征向量,以供模型管道测试。想象一下能够实时更新这个特征库。然后,模型会即时测试特征。呼叫中心呼叫前几秒钟发生了什么交易?放弃前添加到订单中的最后一个行项目是什么?一个网站的最后一次点击是什么?有了实时特性库和原生 Spark 数据源,ML 变得更有价值。
使用拼接原生数据源将完整的表摄取到 Spark 数据帧中—来源:拼接机器
建模简易性
在拼接机器平台上建模也同样简单。使用 Spark、Scikit-learn、TensorFlow、H2O 和 PyTorch,您可以在您最喜欢的库中工作。对于 Scikit-learn,我们可以利用 skdist 在 Spark 上同时训练许多 scikit-learn 模型(想想交叉验证模型)。未来的工作将包括 TensorflowOnSpark。
MLOps
所有这些伟大的特性都带来了很多复杂性。现在,您可以完全访问您的数据和建模工作,您如何围绕这一点建立一个团队呢?你如何组织你的工作?你如何维护治理?**你如何部署你的模型?**这些问题以及其他许多问题让术语 MLOps 进入了人们的视野。MLOps 是为生产就绪型团队实施数据科学工作流的流程。数据科学不同于其他工程工作,因为它是一个实验过程,需要不同的结构和组织方法。
第一遍
使用典型的数据科学家电子表格 —来源:拼接机
如果你曾经见过这样的电子表格,你就知道它的创建有多恐怖。这是一个典型的数据科学运行手册电子表格。不同的实验、运行、参数、数据集、指标以及数据科学实验所需的一切都存储在一个 excel 电子表格中。
这是任何严肃的数据科学团队都无法使用的。创建它需要很长时间,每个新项目都需要重新设计,而且很容易出现用户错误,因为不同的版本作为附件在人们的收件箱里飞来飞去。作为数据科学家,您可能会看着这张工作表,并问“如果我想调整另一个参数,或者尝试随机森林,那将如何适应这张电子表格?”你的恐惧是正确的,这些问题的答案是“你不能”和“它不会”这就是 MLManager 的用武之地
MLManager(和 MLFlow)
干净灵活的 MLFlow 用户界面可以跟踪您的实验,无论多么复杂——来源:Splice Machine
Splice Machine 的 MLManager 采用了流行的开源项目 MLFlow,并添加了我们认为“完成 ML 生命周期”的功能。使用 MLFlow,您可以轻松、动态地跟踪与您的模型尝试相关的一切和一切:参数、度量、训练时间、运行名称、模型类型、图像等工件(想想 AUC 图表),甚至是序列化模型。使用 Splice Machine 的 MLManager,所有这些指标、参数和工件都直接存储在数据库中,无需任何外部存储机制。
您可以轻松地构建大量模型,跟踪您需要的一切,并在 MLFlow UI 中对它们进行数字或视觉比较。例如,上面我们可以看到在一个实验中创建的许多不同运行的指标,下面我们深入研究其中的三个运行,看看我们的随机森林中的树的数量如何与我们的 F1:
用于比较运行和指标的直观用户界面—来源:拼接机
数据科学家甚至可以将他们用来构建模型的笔记本发布到 Github gists,以便共享代码、笔记和片段,供同行审查和透明。给定一个 MLFlow run_id,您可以将模型追溯到它的起源。对于生产就绪的数据科学团队来说,这对于保持对部署哪些模型以及何时部署的控制至关重要。
我们简单明了的 API 使这变得非常容易和可重用,无论您采用什么样的建模过程,只需几个函数调用就可以为您提供完全的可追溯性。我们提供的三个关键功能是:
- log _ feature _ transformations 记录特征向量中的每个特征为达到其最终状态而经历的所有单独转换(即一次性编码、标准化、标准化等)
- log _ Pipeline _ stages——记录 Spark 管道的所有阶段(如过采样、编码、字符串索引、向量组装、建模等)
- log _ model _ params 记录模型的所有参数和超参数
- log _ metrics 记录由我们的内置评估器类确定的模型的所有指标
一旦对模型进行了比较和测试,并且您的团队已经准备好进行部署,下一个主要障碍就要出现了。模型部署是数据科学世界中讨论最多的话题,因为它很复杂,不可概括,并且难以监督。像 AzureML 和 Sagemaker 这样的流行部署机制很难构建到您的管道中,并且很难维护治理。
谁部署了模型?谁可以给模特打电话?在过去的 24 小时里谁给模特打过电话?模特表现如何?如何将新模型集成到应用程序中?如果出于安全考虑,我们不想部署到云中,该怎么办?
这些问题很重要,而且没有直接的答案。借助 Splice Machine 的 MLManager,我们通过在 DB 模型部署中使用来消除复杂性。我们的平台允许您将模型直接部署到数据库中,因此每次在表中插入新行时,您的模型都会立即自动运行,预测会被存储,并跟踪到正在使用的模型。最重要的是,它的部署和利用速度都非常快。部署时间不到 10 秒(相比之下,Sagemaker/Azure ML 部署需要将近 30 分钟)。所有型号的触发器都完全符合 ACID 标准。
在不到 6 秒的时间内将生产模型直接部署到数据库表中— 来源:拼接机
将行插入表中,并立即看到与每一行插入链接的预测,以实现全面的 ACID AI 治理——来源:拼接机
在 DB 模型部署中,安全性和治理对于您的 DBA 来说也很容易学习,因为您的模型预测就在一个表中,就像数据库中的任何其他表一样。想要撤销对模型的访问权限吗?撤销对表的访问。想要收集模型的统计数据吗?只是一些 SQL 查询。想在应用程序中用一个模型替换另一个模型吗?更改查询中的表名。没有学习曲线,因为没有新技术在使用。此外,由于这只是持久数据,您也可以围绕该模型设计微服务
这是怎么做到的?我们的 MLManager 确定模型管道的类型和结构,创建特定于数据集和模型的表和触发器,并将它们部署到您选择的表中。触发器是动态生成的,当新记录插入到所选的表中时,它会立即运行。触发器调用一个生成的存储过程,该存储过程反序列化模型并将其应用于新记录,并将预测写入预测表。数据科学家和数据工程师不必学习 RDBMS 触发器和存储过程,因为它们是自动生成的。例如,下面是为模型部署生成的触发器和函数:
为直接在数据库的表中运行模型而创建的触发器—来源:拼接机器
然而,如果您的团队仍然希望部署到 Sagemaker 或 AzureML,MLManager 也支持这一点,包括部署 UI 和 API 调用。
将模型部署到 AWS Sagemaker 或 AzureML 的一行代码——来源:Splice Machine
用于将模型部署到 AWS Sagemaker 或 AzureML 而无需代码的 job tracker UI——来源:Splice Machine
如今,人工智能在商业成功方面变得越来越谨慎。没有它,你的公司将会被更敏捷、市场准备更充分的竞争对手甩在后面。由于糟糕的模型开发环境和部署时间的严重风险和影响,注入人工智能已经成为成功的最大障碍。您不能冒险将一个糟糕的模型投入到正在做出关键任务决策的生产中。然而,有了 Splice Machine,治理被完全集成,实验比以往任何时候都更容易,幕后管道也成为过去。
例如,考虑一家保险公司。当监管机构开始质疑你在承销中使用的模式时,Splice Machine ML Manager 如何帮助你遵守规定?您可以通过时间旅行回到您训练模型时存在的数据库的“虚拟快照”,并显示进入模型的要素以及当时这些要素上的数据的统计分布。Splice 使数据科学家能够在培训时将我们的 MVCC 事务引擎的快照隔离时间戳作为元数据记录在 MLFlow 中。通过时间旅行,您可以在几分钟内证明您的合规性。
借助我们的单点治理架构构建您可以信赖的模型,并访问完整、公正的数据,轻松、快速地部署模型,并在您的任务关键型应用程序中利用它们,没有延迟,并且与所有其他表具有相同的 ACID 合规性。无缝集成您的模型,无需学习任何新的架构或平台,您的模型只是一个(超级)表格。使用与监视任何其他表相同的技术来监视您的模型,当新模型准备好替换它时,可以立即重新部署。不要再为您想要现代化的每个应用程序重复创建轮子。
为了亲身体验 Splice Machine 和 ML Manager 2.0,观看演示视频,该视频强调了 Splice Machine 的强大功能,以及它如何帮助您构建生产就绪的 ML 模型。
在 Ubuntu 20.04 LTS Focal Fossa 上从源代码构建 python
了解如何在基于 Linux 的操作系统上从源代码构建您最喜欢的语言。
在你最喜欢的操作系统上构建你最喜欢的语言
除了面临依赖问题的用户之外,这个博客是为任何想尝试在任何基于 Linux 的操作系统上从源代码安装 python 的人准备的
如果你已经升级了你的操作系统,并且想知道为什么一些在 Ubuntu 18.04 上很容易安装的库不能在 20.04 上安装。当我的一个开发环境需要一个库,而我试图使用内置 python 来安装这个库时,我遇到了同样的问题。不幸的是,我面临着依赖问题。
这背后的原因是 Ubuntu 20.04 带有内置的 python 版本 3.8.2,而 Ubuntu 18.04 带有 Python 3.6.x
您可能会尝试安装不同的依赖于 Python 3.6 的库。别担心,这很容易…2…3
让我们从一个特定的 python 版本开始,我将在这篇博客中使用 Python 3.6.9,因为我是在以前的系统上工作的。
重要的事情先来
安装构建库所需的基本依赖项
sudo apt-get update
sudo apt-get install -y build-essential checkinstall
和
sudo apt-get install libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev
让我们建造
从改变目录开始,从 python 下载源代码。
我将从 /usr/src 安装它,但是你可以使用你选择的任何位置
cd /usr/src
sudo wget https://www.python.org/ftp/python/3.6.9/Python-3.6.9.tgz
正在提取下载的包
sudo tar xzf Python-3.6.9.tgz
编译源
cd Python-3.6.9
sudo ./configure --enable-optimizations
在这里,我们添加了一个新标志--enable-optimizations
,它设置了默认的 make targets 来启用 Profile Guided Optimization (PGO ),并可用于在某些平台上自动启用链接时间优化(LTO)。
关于--enable-optimizations
的更多详情,请参见 此处的
我们都在等待的一步
构建 python
sudo make altinstall
当我们可以使用install
时,为什么要使用altinstall
原因:在 Unix 和 Mac 系统上,如果你打算安装多个版本的 python,你必须注意你的主要 Python 可执行文件不会被不同版本的安装覆盖
。altinstall
构建特定版本的 python 而不覆盖你的默认版本,这样你就可以使用多个版本的 python。注:根据设备配置,此步骤可能需要一些时间
最终输出将是这样的
python 版本将用后缀python3.6
构建
验证安装
python3.6 --version
注意我用了python3.6
来代替。如果你尝试运行python3 --version
,它会显示内置的 python 版本,从 Ubuntu 20.04 开始是 3.8
注意区别
用一个命令构建一切
我为此创建了一个 shell 脚本,您可以从这里下载脚本文件。
[## novas ush/build-python-from-source
用于从源代码构建 python 的 Shell 脚本…
github.com](https://github.com/novasush/build-python-from-source)
升级 pip(可选)
为 pip 安装依赖关系
sudo apt-get install -y python3-distutils python3-testresources
下载 get-pip.py 并运行
cd ~/
wget [https://bootstrap.pypa.io/get-pip.py](https://bootstrap.pypa.io/get-pip.py)
sudo python3.6 get-pip.py
就像python3.6
这样使用前缀pip3.6
进行安装
pip3.6 安装包
奖金
如果你对每次使用python3.6
或pip3.6
感到厌烦,就使用下面提到的任何一种配置。
使用别名
alias py36=python3.6
alias pip36=pip3.6
python3.6 和 pip3.6 的别名
别名命令的限制:
要么在每次运行 python 或 pip 之前运行该命令,要么在~/.bashrc
的底部添加这些行,但它将仅在用户级别运行。
解决这个问题的最佳方案是使用update-alternatives
使用更新替代方案
检查 python 路径,将其添加到更新替代配置中
which python3.6
我的路径是**/usr/local/bin/python 3.6**
添加更新替代配置的路径
sudo update-alternatives --install /usr/bin/python python /usr/local/bin/python3.6 0
这是所有的乡亲
用 Python 框架构建推荐系统引擎
理解如何使用和开发案例推荐器的实用指南
马丁·范·登·霍维尔在 Unsplash 上的照片
科学界已经提出了许多推荐系统的框架,包括不同的编程语言,如 Java、C#、Python 等。然而,它们中的大多数缺乏包含聚类和集成方法的集成环境,这些方法能够提高推荐准确度。一方面,聚类可以支持开发者预处理他们的数据,以优化或扩展推荐算法。另一方面,集成方法是以个性化方式组合不同类型数据的有效工具。
在本文中,我描述了一个名为案例推荐器的 python 框架,它包含各种基于内容的协作推荐算法,以及用于组合多种算法和数据源的集成方法。此外,它还提供了一套流行的评价方法和指标,用于评分预测和项目推荐。
语境化
传统上,推荐系统使用过滤技术和机器学习信息从用户简档的表示中产生适合用户兴趣的推荐。然而,其他技术,如神经网络、贝叶斯网络和关联规则,也在过滤过程中使用。目前使用最多的过滤类型是:基于内容的(CBF),负责基于元素的内容过滤来选择信息,例如,由于包含不想要的单词而被过滤为垃圾的电子邮件消息;协同过滤(CF),基于人与人之间的关系以及他们对要过滤的信息的主观看法。基于发件人和收件人之间的关系选择电子邮件就是一个例子。此外,还有一种结合了基于内容和协同过滤方法的混合方法。
案例推荐器
案例推荐器目前正在重写,以支持使用已知 Python 科学库的优化计算。除了使用元数据和外部信息,我还开发和改进了允许开发人员操作文件、预测和评估模型、计算用户或项目之间的相似性(不相似)的类。该框架现在在 Python 3 中实现,它解决了推荐系统中的两个常见场景:评级预测和项目推荐,在几个推荐策略中使用显式、隐式或两种类型的反馈。要在 Mac OS X / Linux/ Windows 上安装 Case Recommender,以下两个命令中的一个可能对开发者和用户有用:
easy_install CaseRecommender
或者:
pip install CaseRecommender
在设计我们的框架时,重要的特性是支持大规模的推荐计算,容易为不同类型的过滤和场景创建和扩展算法。另一个特性是支持稀疏和大型数据集,尽可能减少存储数据和中间结果的开销。开发人员可以选择使用一种可用的推荐算法,或者使用一种可用的集成技术组合多个推荐,或者使用 BaseRatingPrediction 或 BaseItemRecommendation 类开发自己的算法。到当前版本为止,框架中可用的算法如下表所示:
输入数据
该框架允许开发者处理不同的数据集,而不必开发他们自己的程序来执行推荐器功能。算法的输入要求数据采用简单的文本格式:
用户标识项目标识反馈
其中 user_id 和 item_id 是分别表示用户和项目 id 的整数,而 feedback 是表示用户有多喜欢某个项目或二元交互的数字。值之间的分隔符可以是空格、制表符或逗号。如果超过三列,则忽略所有附加列。例如,下面是来自 ML100k 数据集的数据示例:
推荐系统的数据集
在学习期间,我还为遥感建立了一个高质量的以主题为中心的公共数据源库。它们是从栈溢出、文章、推荐网站和学术实验中收集整理的。存储库中提供的大多数数据集都是免费的,有开放的源链接,然而,有些不是,你需要请求许可才能使用或引用作者的工作。访问此链接获取这些数据。
这是一个用于推荐系统(RS)的以主题为中心的高质量公共数据源的存储库。他们是…
github.com](https://github.com/caserec/Datasets-for-Recommender-Systems)
使用
对于使用折叠交叉验证划分数据集:
from caserec.utils.split_database import SplitDatabaseSplitDatabase(input_file=dataset, dir_folds=dir_path, n_splits=10).k_fold_cross_validation()
运行项目推荐算法(例如:ItemKNN)
from caserec.recommenders.item_recommendation.itemknn import ItemKNNItemKNN(train_file, test_file).compute()
运行评级预测算法(例如:ItemKNN)
from caserec.recommenders.rating_prediction.itemknn import ItemKNNItemKNN(train_file, test_file).compute()
评估排名(预测@N、回忆@N、NDCG @N、地图@ N 和地图总计)
from caserec.evaluation.item_recommendation import ItemRecommendationEvaluation
ItemRecommendationEvaluation().evaluate_with_files(predictions_file, test_file)
评估排名(梅和 RMSE)
from caserec.evaluation.rating_prediction import RatingPredictionEvaluationRatingPredictionEvaluation().evaluate_with_files(predictions_file, test_file)
在折叠交叉验证方法中运行 ItemKNN
from caserec.recommenders.item_recommendation.itemknn import ItemKNN
from caserec.utils.cross_validation import CrossValidation## Cross Validation
recommender = ItemKNN(as_binary=True)
CrossValidation(input_file=db, recommender=recommender, dir_folds=folds_path, header=1, k_folds=5).compute()
更多例子可以在这个链接中找到。
结论
案例推荐的目标是集成和促进不同领域的新推荐技术的实验和开发。我们的框架包含一个推荐引擎,它包含基于内容的、协作的和混合的过滤方法,用于评级预测和项目推荐场景。此外,该框架包含集成和聚类算法、验证和评估指标,以提高和衡量推荐的质量。
未来的版本计划将包括更多的功能(并行处理技术和新算法)和一个评估工具,该工具有几个图和图形,以帮助开发人员更好地了解他们的推荐算法的行为。
有用的链接
案例推荐器是许多流行的推荐算法的 Python 实现,包括隐式推荐算法和…
github.com](https://github.com/caserec/CaseRecommender) [## MyMediaLite 推荐系统库
MyMediaLite 是公共语言运行时(CLR,通常称为。网)。它解决了…
www.mymedialite.net](http://www.mymedialite.net/) [## 案例推荐|第 12 届 ACM 推荐系统会议录
本文提出了一个改进的基于 Python 的开源推荐框架&案例推荐器
dl.acm.org](https://dl.acm.org/doi/10.1145/3240323.3241611)
在 Google Cloud 上用 Dockers 构建无服务器 Python 数据 API
在本文中,我将向您展示一种简单的方法,可以在几分钟内构建几个数据 API 来利用来自 BigQuery 数据集的数据。这些 API 将通过 dockers 部署,dockers 使用一个名为 Cloud Run 的 GCP 无服务器服务。
体系结构
体系结构
背后的想法是使用无服务器组件。首先,让我们了解这些服务及其在架构中的用途。
- **云运行:**云运行是一个完全托管的计算平台,可以自动扩展你的无状态容器[ 云运行文档 ]。它将处理所有的 API 请求,因为它是完全托管的,我们不需要担心伸缩性。为了实现第一个目标,必须部署 docker。
- **云壳:**云壳为您提供直接从浏览器【云壳 Doc】命令行访问您的云资源。总是需要一个开发环境,在这种情况下,是云壳。python 代码将在这里开发。
- **云构建:**云构建是一种在谷歌云平台基础设施上执行构建的服务。云构建根据您的规范执行构建,并生成 Docker 容器[ Doc ]之类的工件。当我们准备好构建 docker 时,我们将调用 Cloud Build 来完成这项工作,默认情况下,这个 docker 将发布在 GCP 容器注册中心。
- **容器注册:**在 Google 云平台上提供安全、私有的 Docker 图像存储[ Doc ]。在这个地方将托管我们的 docker,准备由云构建调用。
- BigQuery: BigQuery 是 Google 完全托管的、Pb 级、低成本的分析数据仓库。BigQuery 是 NoOps——不需要管理基础设施,也不需要数据库管理员。BigQuery 是我们的数据仓库,所以 API 需要的所有数据都在这里。
获取数据
对于这个项目,我们将使用一个名为 covid19_ecdc 的 BigQuery 公共数据集,其中包含按国家和日期分列的确诊病例和死亡数据。
资料组
创建数据集并查看
让我们创建一个 BigQuery 数据集和一个视图或一个物化视图来获取项目中的数据。
创建视图和数据集
数据
做一个简单的探索,我们可以确定模式和数据。对于这个项目,我们的 API 将提取
- 所有的数据
- 特定日期来自所有国家的案例
- 特定国家报告的所有病例
使用的表格
样板工程
用 python 构建 REST API 并不难。为了简单起见,我发布了一个包含所有文件的 GitHub 项目。
[## antoniocachuan/data API-big query
用 python 构建一个无服务器数据 API,从 big query-antoniocachuan/data API-big query 获取数据
github.com](https://github.com/antoniocachuan/dataapi-bigquery)
让我们了解一下文件
项目文件
App.py
主文件。Flask base 项目,这包括其他库,用 flask_sqlalchemy 和 flask_marshmallow 制作一个更简单的 API。
app.py
需要修改的代码行包括您的 GCP ID 项目、数据集和您的 GCP 凭据。了解更多关于如何获得 GCP 证书的信息。
在项目中更新此变量
Dockerfile
一个Dockerfile
是一个文本文档,它包含用户可以在命令行上调用的所有命令,以组合一个图像。
这个项目使用一个瘦 python 映像,安装所有需要的库,最后用 gunicorn 启动一个 HTTP 服务器来处理 API 请求。
Dockerfile 文件
Requirements.txt
项目所需的所有 python 库的列表。
设计 API 请求
对象关系映射
我们使用 SQLAlchemy 作为 ORM,将 Python 类转换成 BigQuery 表。Marshmallow 用于使对象序列化和反序列化更容易。
要使这段代码工作,定义类名等于 BigQuery 表是很重要的(默认情况下,内部的 Covid 将更改为 covid 以在 BigQuery 上搜索该表)。如果您有其他表名,请在第 23 行使用__table__='YOUR_TABLE_NAME'
。
让我们为你定义所有你想被调用的列。
请求
为了实现定义的三个数据需求,我们编写了 3 个函数。
首先,我们的数据 API 返回 BigQuery 表中的所有数据。
获取所有国家数据
函数get_day()
接收像2020-04-04
这样的参数,并返回该特定日期来自所有国家的数据。
特定一天所有国家的数据
函数country_detail()
像PE
一样接收国家地理编码,并从特定国家返回数据。
使用云构建构建 docker 映像
每次我们需要更新我们的项目,你需要建立一个新版本的 docker 图像。存在许多替代方案,这里我们使用 Cloud Build 工具来构建 docker 映像并将其发布到容器注册表。别忘了定义你的 DOCKER_IMAGE_NAME 。
gcloud builds submit — tag gcr.io/YOUR_PROJECT_ID/DOCKER_IMAGE_NAME
我在 Cloud Shell 上运行它,并记住与您的 Dockerfile 位于同一层,并且您的 project_id 必须被解决(您需要看到黄色的字母)
构建 Docker 映像
如果一切顺利,你可以看到你的 Docker 图片发布。
集装箱登记处
使用云运行部署 docker
最后一步是在 Web 上部署 Docker 映像!Cloud Run 会负责一切,决定一个 SERVICENAME ,更新对你 Docker 镜像的引用。
gcloud run deploy SERVICENAME --image gcr.io/YOUR_PROJECT_ID/DOCKER_IMAGE_NAME --platform managed --region us-central1 --allow-unauthenticated
最后,您获得一个公共 URL 来开始获取数据!
数据 API 工作
是时候通过浏览器获取发出请求的数据了。
所有国家的数据
https://datahackservice-xx-uc.a.run.app/countries
特定日期的数据
https://datahackservice-xx-uc.a.run.app/day/2020-05-01
特定国家的数据
https://datahackservice-xx-uc.a.run.app/country/PE
结论和未来工作
本文向您展示了使用 Python 和无服务器产品(如 Cloud Run 和 BigQuery)开发数据 API 是多么容易。
有了这个 API,我们就能构建出色的数据可视化,数据 API 强大的一个很好的例子来自《世界银行评论》Sébastien Pierre 的文章这里。
特别感谢 Martin Omander 和 Sagar Chand 提供的优秀知识库和文章帮助我开发了这篇文章。
PS 如果你有什么问题,或者有什么有趣的数据想法,可以在 Twitter 和 LinkedIn 上找我。另外,如果你正在考虑参加谷歌云认证,我写了一篇技术文章描述我的经验和建议。
在按照谷歌推荐的 12 周准备后,我通过了云工程师助理考试,在这里…
towardsdatascience.com](/how-i-could-achieve-the-google-cloud-certification-challenge-6f07a2993197)
使用自动旋转构建最先进的机器学习模型
自动登录和自动
AutoGluon 是由 AWS 构建的开源 AutoML 框架,它支持简单易用且易于扩展的 AutoML。它使您能够在没有专业知识的情况下,通过利用最先进的深度学习技术来实现最先进的预测准确性。这也是一种快速的方式来原型化您可以从您的数据集获得什么,以及获得您的机器学习的初始基线。AutoGluon 目前支持处理表格数据、文本预测、图像分类和对象检测。
AutoML 框架的存在是为了降低机器学习的入门门槛。他们负责繁重的任务,如数据预处理、特征工程、算法选择和超参数调整。这意味着,给定一个数据集和一个机器学习问题,继续用不同的超参数组合训练不同的模型,直到找到模型和超参数的最佳组合——也称为 CASH(组合算法/超参数调整)。现有的 AutoML 框架包括 SageMaker Autopilot、Auto-WEKA 和 Auto-sklearn。
AutoGluon 不同于其他(传统)AutoML 框架,它比 CASH(组合算法/超参数调优)做得更多。
集成机器学习和堆叠
在深入研究旋翼飞行器之前,重温一下系综机器学习和堆叠是很有用的。集成学习是一种并行训练许多(有目的地)弱模型来解决同一问题的机器技术。集成由一组单独训练的分类器组成,例如神经网络或决策树,当对新实例进行分类时,它们的预测被组合。这种机器学习技术背后的基本思想是许多模型比少数模型好,不同学习的模型可以提高准确性,即使它们在孤立状态下表现较差。
在大多数情况下,选择单个基本算法来构建多个模型,然后对其结果进行汇总。这也被称为 同质 集成学习方法,就像随机森林算法是最常见和最流行的同质集成学习技术之一,其中多棵树被训练来预测同一问题,然后在它们之间进行多数投票。同类方法的其他例子包括装袋、旋转森林、随机子空间等。
相比之下, 异构 方法涉及使用不同的机器学习基础算法,如决策树、人工神经网络等,来创建用于集成学习的模型。 堆叠 是一种常见的异构集成学习技术。
这个表这里列出了同构和异构机器学习的例子。
AutoGluon 使用了一个多层堆栈集合,我们接下来将研究它是如何工作的。
自动旋转是如何工作的
AutoGluon 在监督机器学习域中运行。这意味着您需要有用于训练的带标签的输入数据。AutoGluon 负责预处理、特征工程,并根据您试图解决的机器学习问题生成模型。
AutoML 的主要部分依赖于超参数调整来生成和选择最佳模型。超参数调整涉及为提供最佳模型的机器学习算法找到超参数的最佳组合。最佳参数集的搜索策略基于随机搜索、网格搜索或贝叶斯优化(SageMaker 使用的方法)。
然而,超参数调谐存在局限性。超参数调优效率低、耗时,而且由于不是所有调优的模型最终都会被使用,因此会造成大量开销浪费。最后,还存在过度拟合验证(保持)数据的风险,因为每次运行调优过程时,都会检查验证数据集,最终会过度拟合验证数据集。
AutoGluon 与其他 AutoML 框架的一个关键区别是,AutoGluon 使用(几乎)每一个经过训练的模型来生成最终预测(而不是在超参数调整后选择最佳候选模型)。
记忆和状态
自动增长是内存感知的,它确保训练的模型不会超过它可用的内存资源。
AutoGluon 是状态感知的,它预计模型在训练期间会失败或超时,并优雅地跳过失败的模型,继续下一个模型。只要你生成了一个成功的模型,自动旋转就可以开始了。
AutoGluon 依赖于多层堆栈集成等策略。它自动进行 k-fold bagging 和 out-of-fold 预测生成,以确保没有过度拟合。具体来说,它利用了现代深度学习技术,也不需要任何数据预处理。
行动中的自动旋转
自动增长也支持文本和图像,但是对于这篇文章,我们主要关注*自动增长表格。*分类和回归的监督机器学习问题的自动增长表格工作。您可以预先指定问题的类型,或者 AutoGluon 会根据您的数据集自动选择一个问题。
数据集
对于数据集,我们使用来自 Kaggle 的流行、开源的 Titanic 数据集。该数据集包含训练数据,其中包括皇家邮轮泰坦尼克号上 851 名乘客的标记数据,以及他们是否在灾难中幸存。该数据集还包括一个测试集,其中有 418 名乘客,但没有标签,即他们是否幸存。
图片来自维基百科
挑战在于根据包括的特征如姓名、年龄、性别、社会经济阶层等来预测乘客是否幸存。
设置
AutoGluon 安装非常简单,只需几行代码
培养
为了开始训练,我们首先从 AutoGluon 导入 TabularPrediction,然后加载数据。AutoGluon 目前可以对已经作为 pandas DataFrames 加载到 Python 中的数据表进行操作,或者对那些存储在 CSV 格式或 Parquet 格式的文件中的数据表进行操作。
一旦您加载了数据,您就可以立即开始训练,您所需要做的就是指向训练数据并指定您想要预测的列的名称。
训练从.fit()
法开始
您可以选择指定希望培训运行的时间,AutoGluon 将在该时间内自动结束所有培训作业。
eval_metric
参数允许您指定 AutoGluon 将用于验证模型的评估指标。默认是accuracy
。
设置auto_stack = True
允许 AutoGluon 管理它将自动创建的堆栈数量。您可以通过stack_ensemble_levels
参数选择指定您想要的堆叠数量。
presets
参数允许您选择想要生成的模型类型,例如,如果延迟和时间不是一个约束条件的话presets='best_quality
通常会生成更精确的模型。另一方面,如果您知道延迟将是一个约束,那么您可以设置presets=[‘good_quality_faster_inference_only_refit’, ‘optimize_for_deployment’
来生成针对部署和推断更优化的模型。
训练开始后,AutoGluon 将在训练的不同阶段开始记录消息。
培训日志(图片由作者提供)
随着培训的进行,AutoGluon 还将记录其生成的各种模型的评估分数。
模型分数(图片由作者提供)
这里需要注意的是,除非您明确需要指定一个验证数据集,否则您应该直接将所有训练数据发送到 AutoGluon。这允许 AutoGluon 以有效的方式自动选择数据的随机训练/验证分割。
训练完成后,您可以开始使用.predict()
功能进行推理。
在上面的例子中,我们只使用默认设置来训练 Titanic 数据集。
我们取得的成绩是最先进的。准确率为 ~78 ,在 Kaggle 的比赛中名列前 8%-10% 。
如果您想使用一个或一些模型,该怎么办
默认情况下,auto glon 会自动选择最佳的多层堆栈集合来运行您的预测,但是,您也可以通过生成一个带有单行代码的排行榜来获得 auto glon 生成的所有模型及其特定性能指标的列表:predictor.leaderboard(extra_info=True, silent=True)
排行榜(作者图片)
除了性能指标,排行榜还显示了每个模型/堆栈的训练和推理时间,以及顺序、祖先、后代等。以及其他信息。
使用此排行榜,您只需指定索引号,即可选择想要运行的特定组合。
例如,在上面的代码中,我们使用了系综的堆栈 19。
超参数调优呢
与您使用其他机器学习框架的经历相反,您可能不需要使用 AutoGluon 进行任何超参数调整。在大多数情况下,通过设置auto_stack = True
或手动指定 stack_levels 和num_bagging_folds
可以获得最佳精度
然而,AutoGluon 支持通过hp_tune = True
参数进行超参数调整。当您启用超参数调整时,AutoGluon 将仅使用您指定了超参数设置的基本算法创建模型。它将跳过其余部分。
在上面的示例代码中,AutoGluon 将训练神经网络和各种基于树的模型,并在指定的搜索空间内调整每个模型的超参数。
结束语
虽然 AutoGluon 可以直接构建最先进的机器学习模型,但我发现它和我的新基线方法一样有用。
虽然 auto glon 会进行数据预处理和特征工程(而且做得非常好),但是您会发现,如果在使用 auto glon 进行训练之前对数据进行预处理和特征工程,您可以获得更好的性能。总的来说,更好的数据几乎总是导致更好的模型。
附加阅读
- AutoGluon-Tabular:针对结构化数据的健壮而精确的 AutoML
- 亚马逊的 AutoGluon 只需几行代码就能帮助开发者部署深度学习模型
- autoglon:深度学习的 AutoML 工具包
构建 Tableau 服务器影响分析报告:为什么和如何
TABLEAU REST API: TABLEAU-API-LIB 教程
一个关注通过构建交互式可视化工具来跟踪数据谱系,从而提高团队生产力的系列
后台数据库的变化会波及到你的场景环境(图片由张凯夫在 Unsplash 上拍摄)
任何 Tableau 环境的支柱都是您输入的数据。俗话说:垃圾进,垃圾出。而您的许多 Tableau 资产(工作簿、数据源等。)代表您的数据生态系统的前端,当后端出现故障时,一切都完了。您的视觉效果是最终用户看到的和与之交互的内容,但这只是冰山一角。
对于许多使用 Tableau 的团队来说,每天都以掷骰子开始。他们将工作簿连接到数据库表,设置提取刷新计划,并希望明天一切都像今天一样顺利。但是希望不是一个好的策略。
在管理 Tableau 服务器环境的上下文中,一个好的策略是确切地知道数据管道中的变化将如何影响 Tableau 工作簿和数据源。在许多组织中,这种意识是存在的——但它往往是非正式的部落知识。
更好的策略是构建交互式 Tableau 工作簿,当您的数据团队对表和模式进行更改时,可以参考这些工作簿,这样任何人都可以准确地跟踪哪些内容和团队将受到影响。换句话说,我们对抗后端更改的最佳武器是构建一些交互式的东西,让我们只需点击一个按钮,就可以可视化数据库更改的影响。
本文是关于如何在 Tableau 中构建影响分析报告的系列文章的第一篇。在这里,我们将概述我们可以获得什么样的信息,概述我们如何利用相关数据,并为一系列教程做好准备,这些教程将介绍如何使用 Tableau 生态系统中的可用工具,将 Tableau 环境中的元数据转化为有价值的资产,从而提高所有 Tableau 相关工作流和团队的工作效率。
在接下来的几个星期里,这些教程将会被发布,链接也会被添加到这里。我们可以把这篇文章看作是引言和目录。
我们为什么要构建自己的影响分析报告?
在众多 Tableau 环境中担任顾问多年后,我还没有看到一个通用的解决方案可以全面解决每个环境的独特需求。
这并不是说不存在一刀切的解决方案。它们确实存在。然而,很难找到一个对千篇一律的解决方案充满热情的客户。每个组织的数据管理都是独一无二的,围绕其数据生态系统演变的复杂性通常需要复杂的解决方案来充分满足其目标。祝你好运从盒子里拿出那种解决方案。
话虽如此,在标准的 Tableau 服务器部署中,有一些现成的产品可供您使用,但需要付费。其中一个工具是数据管理插件 (DMAO)。这个产品(特别是它的元数据方面)值得了解。
我鼓励你尝试一下 DMAO,Tableau 在开发新工具方面一直做得很好。然而,这些工具被设计成普遍适用于任何环境,因此它们可能不会触及您的团队的具体痛处。
让我们更仔细地看看 DMAO 提供了什么,因为这将有助于将我们的对话引向底层的元数据 API ,以及它在构建定制的影响分析报告时对我们的宝贵价值。
一个新的 Tableau 产品:数据管理插件
DMAO 是 Tableau 产品系列中的许可产品。它包括 Tableau 目录和 Tableau 准备指挥。
用 Tableau 自己的话说,他们是这样定义产品组合的:
数据管理插件是一个特性和功能的集合,可帮助客户在其 Tableau 服务器或 Tableau 在线环境中管理 Tableau 内容和数据资产。
您可以使用 Tableau Prep Conductor 利用 Tableau Server 或 Tableau Online 的计划和跟踪功能来自动更新流输出。
Tableau Catalog 包含在数据管理附加组件中,使您可以在数据管理空间中使用各种附加功能。您可以使用 Tableau Catalog 来发现数据、管理数据资产、传达数据质量、执行影响分析以及跟踪 Tableau 内容中使用的数据的沿袭。
好奇那看起来像什么吗?Tableau Prep Conductor 超出了本文的范围,因为它旨在为 Tableau 用户提供一个熟悉的界面来管理他们的数据管道,所以让我们把重点放在 Tableau 目录上。
数据沿袭:跟踪数据的来源和在环境中的使用位置。
在上图中,我们可以看到 Tableau 目录为我们探索各种资产之间的关联提供了一个前端界面。底层元数据知道如果您使用工作簿 A,它将引用数据源 B。同样,数据源 B 接入数据库 C 并使用表 X、Y 和 z。想知道表中存在的列吗?没问题。想知道有多少其他工作簿使用这些表?没问题。
这些元数据对你来说是非常有用的。最棒的是,即使你不拥有 DMAO 的产品,你也可以通过元数据 API 使用底层元数据。这意味着你可以自己获取数据,并随意使用。自由!
如果你在 2019.3 或更高版本上,元数据 API 是标准的,包括电池。如果您想深入研究,请查看这篇关于使用元数据 API 的教程。
元数据:你在 Tableau 世界的新朋友
虽然它确实有一个未来的环,元数据不仅仅是一个时髦的词。在 Tableau 的上下文中,元数据提供了关于你的内容、你的用户、如何访问或使用内容的信息,以及几乎所有介于两者之间的信息。
作为元数据的一个例子,考虑一下这个:Tableau 最近举办了一个社区挑战赛,邀请参与者使用元数据扫描 Tableau 服务器上的工作簿,识别命名不当的计算,并向工作簿的所有者发送电子邮件,要求他们重命名标记的计算。
在 Tableau Server 版本 2019.3 中引入元数据 API 之前,以直接的方式解决这一挑战是不可能的。能够查看工作簿的内部并查询其计算字段的详细信息是元数据 API 赋予我们所有人的一个愿望。
这仅仅是皮毛。我们从 Metdata API 中获得的另一个强大功能是查看我们的工作簿和数据源,以查看为我们的视觉效果提供支持的数据库资产。
我甚至数不清有多少次听到一位分析师抱怨被要求编制一份 Tableau 中使用的所有数据库表的列表。当你思考这个问题时,从管理的角度来看,这似乎是一个很好的问题:我们的 Tableau 仪表板中使用了什么?我们如何确保人们使用的是正确的 T4 数据来源?我们如何确保在不破坏仪表板的情况下修改数据库中的表是安全的?如果我们从数据库中删除旧表,会破坏多少仪表板?
在过去,这些问题会在一些负担过重的分析师任务清单的积压中痛苦地死去。今天不行。今天,您只需几个问题和一个仪表板,就能得到所有这些问题的答案。
介绍我们影响分析节目的演员阵容
舞台已经布置好了,让我们给你介绍一下演员。这部剧里有三位明星你会想熟悉的:
- 元数据 API
- REST API
- Tableau 服务器仓库(工作组 PostgreSQL 数据库)
这些是我们将在接下来的教程中使用的工具,用于从我们的 Tableau 环境中收集数据并构建影响分析仪表板。我们从我们的 Tableau 环境中提取数据,将数据反馈到 Tableau 视觉效果中,并使用这些视觉效果来更好地管理我们的 Tableau 环境。我怀疑“元”这个词再合适不过了。
在我们需要识别内容之间关系的任何场景中,元数据 API 都将是主角。虽然这些信息中的一些也可以从 REST API 中提取,但元数据 API 受益于 GraphQL,这是一种查询语言,它让我们能够以 REST API 实现不支持的方式动态定义查询。元数据 API 允许我们准确地定义我们想要拉取的数据,并使搜索 Tableau 环境的每个角落成为可能。
我不是说给 REST API 蒙上阴影;它有自己的角色。事实上,REST API 是我们进入元数据 API 的网关,也是我们所有查询都要通过的载体。REST API 允许我们以自动化的方式获取数据并发布、更新或删除内容。使用 REST API,我们将能够将从元数据 API 提取的数据发布为 Tableau 数据源文件或摘录。在构建我们的影响分析时,这是非常必要的一步——我们需要将数据源插入到我们的仪表板中。
这个故事中的第三个火枪手是 Tableau Server 的内部 PostgreSQL 数据库。这包含了丰富的信息,虽然我们需要的 90%将来自元数据 API 和 REST API,但是存储库数据库将为我们的影响分析添加有价值的上下文。例如,我们可以添加一些信息来描述特定内容的访问频率、用户对特定内容采取的操作、哪些内容是以交互方式而不是通过电子邮件订阅来访问的,等等。当需要真正定制您的影响分析时,PostgreSQL 数据库将是您的得力助手。
既然我提到了火枪手…第四个火枪手(达达尼昂)应该是超级 API。这更像是客串,更像是一种最佳实践,而不是必须的。我们不会将 CSV 文件作为影响分析仪表板的数据源发布,而是使用 Hyper API 来创建。超级文件。这些是高性能的提取,API 支持将数据附加到。超级文件。这为跟踪我们元数据中的历史趋势打开了大门,而不需要担心将我们的数据存储在一个巨大的 CSV 中,或者担心将数据存储在数据库中。
影响分析路线图和里程碑
我们将在此发布更新。
里程碑 1 (ETA 是 4/24/2020):使用元数据 API 为我们的工作簿(视图和仪表板)、数据源和流收集数据。对于这三类内容中的每一类,我们将确定所有相关的数据库资产。
里程碑 2 (ETA 是 4/29/2020):在里程碑 1 的基础上,使用来自 Tableau 服务器存储库的补充数据,提供与每个工作簿(视图和仪表板)、数据源和流程相关联的交互数量。
里程碑 3 (ETA 为 5/6/2020):使用 hyper API 将里程碑 1 和 2 的组合输出转换为. Hyper 提取,并将内容发布到 Tableau 服务器(或 Tableau Online)。
里程碑 4 (ETA 是 5/13/2020):使用里程碑 3 中发布的数据源构建我们的第一个影响分析仪表板。
里程碑 5 (TBD)
包装它
本文到此结束,并为我们探索构建定制的视觉效果,为我们的团队提供易于使用的影响分析仪表板打下基础!请继续关注我们在上面列出的里程碑上的复选框。
希望使用 Python 来开发 Tableau APIs 吗?
如果你想开始使用 tableau-api-lib ,我用来与 tableau 的 api 交互的 Python 库,这里有几个相关的教程。
tap 上的 Tableau 服务器:使用个人访问令牌进行认证
Tableau Server on tap:向元数据 API 发送 GraphQL 查询
构建放射学性别差异的道琼斯指数
Twitter 上的数据流提供了丰富的信息来源,可以帮助我们实时了解趋势和观点。
人们可以很容易地以自动化的方式利用 Twitter 数据,并创建大型的结构化数据集。这种能力直觉上很强大——我们能从无休止的患者/医生聊天中学到什么?—我花了相当多的时间思考最高产量的用例。
我的结论是,Twitter 数据的一个特别有意义的应用是理解放射学中的性别差异,以及它们是如何随时间变化的。这是有意义的,因为:
- 有一场强大的运动专注于纠正放射学中的性别差异,这个领域在吸引和培养女性人才方面历来表现不佳。这种论述是围绕一系列离散的、确定的标签组织的,比如#RadXX 和# WomenInRadiology。
- 这个讨论代表了围绕放射学的更大的 Twitter 讨论的一个子集,这是一个每天有成千上万条推文的强大对话。因此,我们可以考虑女性赋权放射活动与整个放射活动的某种比例。
利用这一推理,我决定建立 RadX 指数(RXI),这是一个类似道琼斯的定量指数,为我们在纠正放射学性别差异方面的表现提供了一个动态的实时标志。你可以在这里查看:【https://bit.ly/320uK9D
简单的 1.0 版本是这样工作的:
- 包括#RadXX 在内的所有推文都放在一个数据库中,带有时间戳和其他关键信息,如用户名和推文 URL。
- 包括#Radiology 在内的所有推文都放在一个类似的数据库中。
- 每 24 小时,每个数据库(#RadXX 和#Radiology)中的推文数量被量化,并取一个比率来形成 RXI。
- 当天的 RXI 以经典的股票代码方式自动绘制,提供了一个公开的图形索引,显示我们的表现以及随时间的变化。
包含#radiology 的推文组织在数据库中进行分析
我们正在有效地创建以下内容的自动近似:
这种方法简单且不完美,但它是一个开始。有许多方法可以使它变得更复杂,例如:
- 对女性和男性放射科医生反应的情感分析。放射科对女性是什么态度?
- 量化男性和女性放射科医生的推文数量。有足够数量的女性放射科医生参加讨论吗?
- 围绕#RadXX 推文的参与度分析(例如,浏览量、点赞数、RTs)。参与度越高,RXI 越高。
- #RadXX 和#Radiology 不一定是 RXI 比率的最佳标签。例如,将#RadXX、# WomenInRadiology 和#HeForShe 结合使用可能比单独使用#RadXX 更好。
可以整合这些指标,以创建一个更复杂的主 RXI 得分,该得分优于 1.0 迭代(在一天内构建,以开始生成数据和讨论)。
你怎么想呢?我很想让这个指数开源,并共同努力来改善它。我的方法是粗略的第一步,但你可能比我聪明…所以,你能想到更好的 RXI 实现吗?大家讨论一下!
如果你想就这个项目或医疗保健/技术领域的任何其他事情进行交流,请联系 Twitter 或LinkedIn。如果你喜欢这篇文章,请分享…讨论将使这个想法变得更好!
使用 AutoAI 构建 Kaggle 项目的“Hello World”
从这个博客开始你的 Kaggle 之旅,并使用 Watson AutoAI 充分利用它
埃里克·克鲁尔在 Unsplash 上的照片
总是有人问我,开始研究数据科学的最佳方式是什么?嗯,我对此的回答总是一样的,做大量的数据科学课程来熟悉概念,然后通过参加 Kaggle 竞赛来继续构建项目。在这篇博客中,我将展示我是如何使用 AutoAI 在 30 分钟内完成泰坦尼克号生存 Kaggle 比赛,从而开始我的 Kaggle 之旅的。
什么是 Kaggle 比赛?
Justin Chan 关于数据驱动型投资者的照片
Kaggle 为数据科学爱好者提供了一个分析比赛的平台,公司和研究人员发布数据,让爱好者竞争,以产生预测和描述数据的最佳模型。目前有超过 13,000 个数据集,Kaggle 提供了一个名副其实的数据金矿。
为什么选择 IBM Watson AutoAI?
Watson Studio 中的 AutoAI 可以自动完成通常需要数据科学家几天或几周时间才能完成的任务。您所需要做的就是提交您的数据,剩下的就交给工具来决定最适合您项目的管道。
格雷格·菲拉在 IBM 沃森上的照片
让卡格灵开始吧!
泰坦尼克号项目被称为 Kaggle 项目的“hello world ”,因为初学者可以在尝试复杂项目之前获得 Kaggle 实践经验。
准备:
- 设置 AutoAI: —创建一个 IBM Cloud 账户。— 创建一个 Watson Studio 实例。—创建项目—在项目中添加 AutoAI。
- 在 Kaggle 上创建账户
- 参加泰坦尼克号生存竞赛
步骤 1 —数据收集:
我们必须从竞赛页面下载数据集。zip 文件将包含 3 个文件 train.csv 文件是我们将用于训练模型的文件,测试是我们将用于批量评分我们的模型并将其用于提交目的的文件,而 gender_ submission 文件显示了 Kaggle 的提交文件应该是什么样子(它是我们将填写的模板)。
第 2 步—数据准备:
数据非常干净,但是在训练数据集和测试数据集中需要注意空值。首先,让我们用 Age 和 Fare 列中值的平均值替换它们中的空值。我使用 Excel 公式“平均值”来查找和替换这些值。我让客舱栏为空。在测试模型时,这一点正在处理中(待续…)
步骤 3 —使用 AutoAI 建立模型:
虽然这一步看起来很难,但却是最简单的,因为我们使用的是 AutoAI。只需在 Watson Studio 中创建一个 AutoAI 项目,为您的实验命名,并上传您的 train.csv 文件。选择“幸存”作为您的预测变量,运行实验,并等待至少 15 分钟。
AutoAI 采用数据集和目标变量来设计管道(这些是不同的模型),使用各种 HPO(超参数优化)参数和增强的特征工程来为每个管道获得最佳模型。
您可能已经知道,有不同的方法来评估和选择最佳模型,如准确度、F1 分数、精确度等。您也可以根据自己的需要对其进行编辑,但是我们将针对这种情况选择准确性(您也可以尝试其他评估者)。在下图中,您可以看到每个管道在不同评估者面前的表现。这就是选择和推荐部署最佳管道(领导者管道)的方式。
现在,要部署模型,请单击第一个管道(带有星号),另存为,然后单击模型。这将允许您将您的模型部署为 Watson 机器学习模型。
回到您的项目,单击 WML 模型并将其部署为 web 服务,部署完成后,您可以测试它并获取我们将在下一步中使用的评分链接。
步骤 4 —对模型进行批量评分
现在我们有了模型,让我们创建一个 python 脚本,根据 test.csv 对 AutoAI 模型进行批量评分,以提交我们的结果。
下面是对测试文件中的所有记录运行批处理分数测试的代码。
代码基本上是将每个记录逐个存储在变量中,这样就可以将它作为有效载荷传递给模型进行评分。由于一些客舱值为空,我们将其替换为空。一旦我们从模型中获得 JSON 结果,我们解析它以获得记录的预测值,并将其存储在数组中,该数组将被写回到 Results.csv 表中。
Results.csv 文件
最后一步——向 Kaggle 提交结果
转到竞赛页面,点击提交结果以提交您的结果文件。等到你出现在领导板上,你的分数(我的分数是 77%)和排名会显示在屏幕上。分数决定你的排名。你可以不断改进你的模型,并不断提交多次到达顶端。
作者在 Kaggle 上的排行榜
瞧💃!!您已经提交了您的第一个 Kaggle 项目!!
我的经历
AutoAI 确实使 kaggling 变得更简单,一个担心是缺乏对参数的控制,并且需要对某些事情进行微调。这可以通过将模型保存为笔记本并用 python 编码来轻松完成。在我看来,Watson AutoAI 是初学者开始 Kaggle 之旅的最佳工具。
快乐的旅行💃!
参考
- https://www.kaggle.com/c/titanic#
- https://dataplatform.cloud.ibm.com/
- 【https://kaggle.com/
- https://medium . com/IBM-Watson/introducing-autoai-for-Watson-studio-241675 FB 0454
构建香农熵公式
每个人理解香农熵的直观方法
凯利·西克玛在 Unsplash 上的照片
目的
本文的写作是试图理解可以使用香农熵公式的决策树算法的结果。本文旨在通过首先用一个例子说明熵,然后一步一步地建立公式,来展示公式背后的直观推理。
熵是什么?
熵是不确定性的度量,由 Claude E. Shannon 引入信息论领域。在这种情况下,可以区分两个相关的量:熵和自熵,前者处理一组事件,后者与单个事件相关。
信息和熵
要记住的一个基本概念是信息和熵是直接相关的。
自熵度量的是事件中包含的不确定性或信息。
当考虑自熵时,高度不确定的事件具有高熵并提供大量信息,而某个事件具有低熵并提供少量信息。
举个极端的例子,一个总会发生的事件有零自熵,而一个永远不会发生的事件有无限自熵。
另一方面,熵测量所有事件对系统贡献的平均自熵。
为了说明这两种熵类型,假设您有一个容器,其中只能放置四个球,可以是绿色或红色,如图 1 所示。这导致了五种配置,其中排序并不重要。
图 1:熵和自熵与提取绿色球的概率相关
考虑到容器配置 1,整个系统的熵为零,因为提取球的事件不存在不确定性,因为球总是红色的。提取红球的自熵为零,对于绿球是无穷大。
在容器配置 2 中,整个系统的熵很低,因为人们很可能从容器中抽取一个红球。在这种情况下,提取绿色球的自熵高,而提取红色球的自熵低。
最后一个重要的情况是容器配置 3,因为它提供了最大熵,因为这两个事件的可能性相等。这两个事件的自熵中等,相等。
容器配置 4 和 5 分别与容器配置 2 和 1 相同,只是颠倒了绿色和红色。
上述事件的自熵可以通过图 2 中的图表来总结。
图 2:与提取绿色球的概率相关的自熵图
同样,两个事件系统的熵可以用图 3 中的图来概括。
图 3:与提取绿色球的概率相关的熵图
测量信息
由于熵和信息是直接相关的,测量一个也会导致测量另一个的方法。
一种更直观的表示信息的方式是使用二进制数字,即比特。例如,整数值可以表示为 0 和 1 的位模式。
像从容器中取出一个球这样的事件或者像“客队赢了比赛”这样的消息也可以用表 1 中的某些位模式来表示。
表 1:不同事件的潜在位模式表示
测量自熵
**比特模式可以与取决于比特数的概率相关联。**例如,任何单个位模式的概率为 1/2,而任何两个位的位模式的概率为 1/4,如此类推,如图 4 所示。
图 4:与位数相关的位模式概率
通常,某个位模式的概率为:
相反,通过在两面都应用以 2 为底的对数:
如图 4 所示,对于[0,1]的整个概率范围,可以获得信息比特的数量,这接近于图 2 中的直觉。
图 5:自熵公式的图表
测量熵
当测量一个系统的熵时,每个事件的平均自熵贡献被考虑在内。
举例来说,考虑包含 10 位信息的事件(或消息)。如果该消息以 0.2 的概率出现,那么它对整个系统的平均贡献是 10 比特* 0.2 = 2 比特因此,考虑到自熵的公式,每个事件的平均贡献是:
包含在 n 个事件及其相关概率的系统中的熵是所有这些平均贡献的总和:
回到第一个示例,考虑两个事件的系统,公式变为:
因为所有概率的总和等于 1,所以上述可以替换为:
在图 5 中画出两个事件系统的熵公式,我们可以再次证实图 3 中的直觉。
图 6:两个事件的熵图
表 2 给出了对两个事件的系统应用自熵和熵公式时得到的 of 值。事件 1 可以与“抽取一个红球”相关联,而事件 2 可以与“抽取一个绿球”相关联,如在所示的例子中。
表 2:双事件系统的自熵和熵值
结论
我希望这能让我们对熵公式是如何建立的有所了解。
回顾本文的目的,决策树使用 Shannon 熵公式来挑选一个特征,该特征将数据集递归地分割成具有高度一致性(低熵值)的子集。更均匀地分割数据集会使决策树深度更小,从而更易于使用。
如果你有意见或建议,我将很高兴收到你的来信!
图 1 中的例子基本上是 Luis Serrano 在文章“香农熵、信息增益和从桶中捡球”中的简化版本
使用决斗式双深度 Q 学习构建终极 AI 智能体
Pytorch 中强化学习的实现。
简介
在过去的几篇文章中,我们已经讨论了和为 VizDoom 环境实现了各种价值学习架构,并检验了它们在最大化回报方面的表现。简而言之,这些包括:
总的来说,vanilla Deep Q-learning 是一种高度灵活和响应性的在线强化学习方法,它利用快速的场景内更新来估计环境中的状态-动作(Q)值,以便最大化回报。Q-learning 可以被认为是一种策略外的 TD 方法,其中该算法旨在选择独立于当前遵循的策略的最高值的状态-动作对,并且已经与 OpenAI Atari 健身房环境的许多原始突破相关联。
相比之下,双重深度 Q 学习改进通过使用双重网络设置将动作选择与 Q 值目标计算分离,解决了在 DQN 观察到的状态-动作值高估的问题,这是之前在训练中观察到的普遍问题。类似地,due Deep Q-learning 通过将单个输出状态-动作流分成单独的价值和优势流,提高了模型在训练期间的概括能力,允许代理学习专注于单独的目标以提高性能。
在我们的 DQN 系列文章的最后一篇中,我们将把迄今为止我们所学的所有内容结合到一个单一的复合方法中——决斗双深度 Q 学习(DuelDDQN)。通过结合我们以前模型的所有优势,我们将致力于进一步提高我们的代理在 VizDoom 环境中的收敛性。
实施
**我们将在与上一篇文章保卫防线相同的多目标条件下,在相同的 VizDoomgym 场景中实现我们的方法。**环境的一些特征包括:
- 一个 3 的动作空间:开火,左转,右转。不允许扫射。
- 向玩家发射火球的棕色怪物,命中率为 100%。
- 试图以之字形靠近来咬玩家的粉红色怪物。
- 重生的怪物可以承受更多伤害。
- 杀死一个怪物+1 点。
- -死了得 1 分。
“防线方案”的初始状态
回想一下,在我们最初的 DQN 实现中,我们已经利用了两个并发网络——一个用于行动选择的评估网络,以及一个定期更新的目标网络,以确保生成的 TD 目标是固定的。我们可以利用这个现有的设置来构建我们的 DuelDDQN 架构,而无需初始化更多的网络。
请注意,由于两个网络定期更新彼此的权重,因此这两个模型仍然是部分耦合的,但重要的是,动作选择和 Q 值评估是由在特定时间步长不共享同一组 a 权重的独立网络完成的。
我们的 Google 协作实现是利用 Pytorch 用 Python 编写的,可以在 GradientCrescent Github 上找到。我们的方法基于泰伯优秀强化学习课程中详述的方法。由于我们的 DDQN 实现类似于我们之前的普通 DQN 实现,所以整个高级工作流是共享的,这里不再重复。
让我们从导入所有必需的包开始,包括 OpenAI 和 Vizdoomgym 环境。我们还将安装火炬视觉所需的 AV 包,我们将使用它进行可视化。请注意,安装完成后必须重新启动运行时。
#Visualization cobe for running within Colab
!sudo apt-get update
!sudo apt-get install build-essential zlib1g-dev libsdl2-dev libjpeg-dev nasm tar libbz2-dev libgtk2.0-dev cmake git libfluidsynth-dev libgme-dev libopenal-dev timidity libwildmidi-dev unzip# Boost libraries!sudo apt-get install libboost-all-dev# Lua binding dependencies
!apt-get install liblua5.1-dev
!sudo apt-get install cmake libboost-all-dev libgtk2.0-dev libsdl2-dev python-numpy git
!git clone [https://github.com/shakenes/vizdoomgym.git](https://github.com/shakenes/vizdoomgym.git)
!python3 -m pip install -e vizdoomgym/!pip install av
接下来,我们初始化我们的环境场景,检查观察空间和动作空间,并可视化我们的环境。
import gym
import vizdoomgymenv = gym.make('VizdoomDefendLine-v0')
n_outputs = env.action_space.n
print(n_outputs)observation = env.reset()
import matplotlib.pyplot as pltfor i in range(22):
if i > 20:
print(observation.shape)
plt.imshow(observation)
plt.show()
observation, _, _, _ = env.step(1)
接下来,我们将定义预处理包装器。这些类继承自 OpenAI gym 基类,覆盖了它们的方法和变量,以便隐式地提供所有必要的预处理。我们将开始定义一个包装器来重复许多帧的每个动作,并执行元素方式的最大值以增加任何动作的强度。您会注意到一些三级参数,如 fire_first 和no _ ops——这些是特定于环境的,在 Vizdoomgym 中对我们没有影响。
class RepeatActionAndMaxFrame(gym.Wrapper):
#input: environment, repeat
#init frame buffer as an array of zeros in shape 2 x the obs space
def __init__(self, env=None, repeat=4, clip_reward=False, no_ops=0,
fire_first=False):
super(RepeatActionAndMaxFrame, self).__init__(env)
self.repeat = repeat
self.shape = env.observation_space.low.shape
self.frame_buffer = np.zeros_like((2, self.shape))
self.clip_reward = clip_reward
self.no_ops = no_ops
self.fire_first = fire_first
def step(self, action):
t_reward = 0.0
done = False
for i in range(self.repeat):
obs, reward, done, info = self.env.step(action)
if self.clip_reward:
reward = np.clip(np.array([reward]), -1, 1)[0]
t_reward += reward
idx = i % 2
self.frame_buffer[idx] = obs
if done:
break
max_frame = np.maximum(self.frame_buffer[0], self.frame_buffer[1])
return max_frame, t_reward, done, info
def reset(self):
obs = self.env.reset()
no_ops = np.random.randint(self.no_ops)+1 if self.no_ops > 0 else 0
for _ in range(no_ops):
_, _, done, _ = self.env.step(0)
if done:
self.env.reset()
if self.fire_first:
assert self.env.unwrapped.get_action_meanings()[1] == 'FIRE'
obs, _, _, _ = self.env.step(1)
self.frame_buffer = np.zeros_like((2,self.shape))
self.frame_buffer[0] = obs
return obs
接下来,我们为我们的观察定义预处理函数。我们将使我们的环境对称,将它转换到标准化的盒子空间,将通道整数交换到张量的前面,并将其从原始(320,480)分辨率调整到(84,84)区域。我们也将我们的环境灰度化,并通过除以一个常数来归一化整个图像。
class PreprocessFrame(gym.ObservationWrapper):
#set shape by swapping channels axis
#set observation space to new shape using gym.spaces.Box (0 to 1.0)
def __init__(self, shape, env=None):
super(PreprocessFrame, self).__init__(env)
self.shape = (shape[2], shape[0], shape[1])
self.observation_space = gym.spaces.Box(low=0.0, high=1.0,
shape=self.shape, dtype=np.float32)
def observation(self, obs):
new_frame = cv2.cvtColor(obs, cv2.COLOR_RGB2GRAY)
resized_screen = cv2.resize(new_frame, self.shape[1:],
interpolation=cv2.INTER_AREA)
new_obs = np.array(resized_screen, dtype=np.uint8).reshape(self.shape)
new_obs = new_obs / 255.0
return new_obs
接下来,我们创建一个包装器来处理帧堆叠。这里的目标是通过将几个帧堆叠在一起作为单个批次,帮助从堆叠帧中捕捉运动和方向。这样,我们可以捕捉环境中元素的位置、平移、速度和加速度。通过堆叠,我们的输入采用(4,84,84,1)的形状。
class StackFrames(gym.ObservationWrapper):
#init the new obs space (gym.spaces.Box) low & high bounds as repeat of n_steps. These should have been defined for vizdooom
#Create a return a stack of observations
def __init__(self, env, repeat):
super(StackFrames, self).__init__(env)
self.observation_space = gym.spaces.Box( env.observation_space.low.repeat(repeat, axis=0),
env.observation_space.high.repeat(repeat, axis=0),
dtype=np.float32)
self.stack = collections.deque(maxlen=repeat)
def reset(self):
self.stack.clear()
observation = self.env.reset()
for _ in range(self.stack.maxlen):
self.stack.append(observation)
return np.array(self.stack).reshape(self.observation_space.low.shape)
def observation(self, observation):
self.stack.append(observation)
return np.array(self.stack).reshape(self.observation_space.low.shape)
最后,在返回最终环境供使用之前,我们将所有的包装器绑定到一个单独的 make_env() 方法中。
def make_env(env_name, shape=(84,84,1), repeat=4, clip_rewards=False,
no_ops=0, fire_first=False):
env = gym.make(env_name)
env = PreprocessFrame(shape, env)
env = RepeatActionAndMaxFrame(env, repeat, clip_rewards, no_ops, fire_first)
env = StackFrames(env, repeat)
return env
接下来,让我们定义模型中的决斗部分,一个深度 Q 网络,具有两个输出流。这基本上是一个三层卷积网络,它采用预处理的输入观测值,将生成的展平输出馈送到一个全连接层,然后将输出分成价值流(单节点输出)和优势流(节点输出对应于环境中的动作数量)。
请注意,这里没有激活层,因为激活层的存在会导致二进制输出分布。我们的损失是我们当前状态-动作的估计 Q 值和我们预测的状态-动作值的平方差。然后,我们附上 RMSProp 优化器,以尽量减少我们在培训期间的损失。
import os
import torch as T
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
class DeepQNetwork(nn.Module):
def __init__(self, lr, n_actions, name, input_dims, chkpt_dir):
super(DeepQNetwork, self).__init__()
self.checkpoint_dir = chkpt_dir
self.checkpoint_file = os.path.join(self.checkpoint_dir, name)
self.conv1 = nn.Conv2d(input_dims[0], 32, 8, stride=4)
self.conv2 = nn.Conv2d(32, 64, 4, stride=2)
self.conv3 = nn.Conv2d(64, 64, 3, stride=1)
fc_input_dims = self.calculate_conv_output_dims(input_dims)
self.fc1 = nn.Linear(fc_input_dims,1024)
self.fc2 = nn.Linear(1024, 512)
#Here we split the linear layer into the State and Advantage streams
self.V = nn.Linear(512, 1)
self.A = nn.Linear(512, n_actions)
self.optimizer = optim.RMSprop(self.parameters(), lr=lr)
self.loss = nn.MSELoss()
self.device = T.device('cuda:0' if T.cuda.is_available() else 'cpu')
self.to(self.device)
def calculate_conv_output_dims(self, input_dims):
state = T.zeros(1, *input_dims)
dims = self.conv1(state)
dims = self.conv2(dims)
dims = self.conv3(dims)
return int(np.prod(dims.size()))
def forward(self, state):
conv1 = F.relu(self.conv1(state))
conv2 = F.relu(self.conv2(conv1))
conv3 = F.relu(self.conv3(conv2))
# conv3 shape is BS x n_filters x H x W
conv_state = conv3.view(conv3.size()[0], -1)
# conv_state shape is BS x (n_filters * H * W)
flat1 = F.relu(self.fc1(conv_state))
flat2 = F.relu(self.fc2(flat1))
V = self.V(flat2)
A = self.A(flat2)
return V, A
def save_checkpoint(self):
print('... saving checkpoint ...')
T.save(self.state_dict(), self.checkpoint_file)
def load_checkpoint(self):
print('... loading checkpoint ...')
self.load_state_dict(T.load(self.checkpoint_file))
接下来,我们将定义我们的代理,它遵循我们之前的 DDQN 实现和 DuelDQN 实现。我们的代理正在使用一个勘探率递减的ε贪婪策略,以便随着时间的推移最大化开发。为了学会预测使我们的累积奖励最大化的状态-行动-值,我们的代理人将使用通过抽样存储的记忆获得的贴现的未来奖励。
您会注意到,作为代理的一部分,我们初始化了 DQN 的两个副本,并使用方法将原始网络的权重参数复制到目标网络中。虽然我们的常规方法利用这种设置来生成固定的 TD 目标,但我们的 DuelDDQN 方法将扩展到这一点:
- 从重放存储器中检索状态、动作、奖励和下一状态(sar)。
- 评估网络用于生成当前状态的优势( A_s )和状态( V_s )值。它还用于为下一个状态生成相应的值。
- 目标网络用于创建下一个状态的优势( A_s_ )和状态( V_s_ )值。
- 当前状态的预测 Q 值( q_pred )是通过对当前状态的优势和状态值求和,并使用评估网络的输出流减去用于归一化的当前状态优势值的平均值而生成的。使用评估网络的输出流,下一状态 re 的评估 Q 值( q_eval )也以类似的方式创建。使用 argmax() 函数获得下一个状态的最大可能性动作。请注意, q_eval 在这里的作用只是关注于动作选择,与明确地参与目标计算无关。
- 使用目标网络的输出流,以类似的方式为下一状态( q_next )创建目标 Q 值。 q_next 的作用是专注于目标计算,不参与动作选择。
- 通过评估网络识别的 max_actions 将当前状态中的回报与从下一状态的目标网络导出的 Q 值相结合,计算当前状态的 TD-target。
- 通过将 TD 目标与当前状态 Q 值进行比较来计算损失函数,然后将其用于训练网络。
import numpy as np
import torch as T
#from deep_q_network import DeepQNetwork
#from replay_memory import ReplayBuffer#Combining the value stream splitting of a DuelDQN with the update approach of a DDQN
class DuelDDQNAgent(object):
def __init__(self, gamma, epsilon, lr, n_actions, input_dims,
mem_size, batch_size, eps_min=0.01, eps_dec=5e-7,
replace=1000, algo=None, env_name=None, chkpt_dir='tmp/dqn'):
self.gamma = gamma
self.epsilon = epsilon
self.lr = lr
self.n_actions = n_actions
self.input_dims = input_dims
self.batch_size = batch_size
self.eps_min = eps_min
self.eps_dec = eps_dec
self.replace_target_cnt = replace
self.algo = algo
self.env_name = env_name
self.chkpt_dir = chkpt_dir
self.action_space = [i for i in range(n_actions)]
self.learn_step_counter = 0self.memory = ReplayBuffer(mem_size, input_dims, n_actions)self.q_eval = DeepQNetwork(self.lr, self.n_actions,
input_dims=self.input_dims,
name=self.env_name+'_'+self.algo+'_q_eval',
chkpt_dir=self.chkpt_dir)self.q_next = DeepQNetwork(self.lr, self.n_actions,
input_dims=self.input_dims,
name=self.env_name+'_'+self.algo+'_q_next',
chkpt_dir=self.chkpt_dir)#Epsilon greedy action selection
def choose_action(self, observation):
if np.random.random() > self.epsilon:
# Add dimension to observation to match input_dims x batch_size by placing in list, then converting to tensor
state = T.tensor([observation],dtype=T.float).to(self.q_eval.device)
#As our forward function now has both state and advantage, fetch latter for actio selection
_, advantage = self.q_eval.forward(state)
action = T.argmax(advantage).item()
else:
action = np.random.choice(self.action_space)return actiondef store_transition(self, state, action, reward, state_, done):
self.memory.store_transition(state, action, reward, state_, done)def sample_memory(self):
state, action, reward, new_state, done = \
self.memory.sample_buffer(self.batch_size)states = T.tensor(state).to(self.q_eval.device)
rewards = T.tensor(reward).to(self.q_eval.device)
dones = T.tensor(done).to(self.q_eval.device)
actions = T.tensor(action).to(self.q_eval.device)
states_ = T.tensor(new_state).to(self.q_eval.device)return states, actions, rewards, states_, donesdef replace_target_network(self):
if self.learn_step_counter % self.replace_target_cnt == 0:
self.q_next.load_state_dict(self.q_eval.state_dict())def decrement_epsilon(self):
self.epsilon = self.epsilon - self.eps_dec \
if self.epsilon > self.eps_min else self.eps_mindef save_models(self):
self.q_eval.save_checkpoint()
self.q_next.save_checkpoint()def load_models(self):
self.q_eval.load_checkpoint()
self.q_next.load_checkpoint()
#Make sure you understand this line by line
#For DDQN main difference is here - Consult the lecture to gain a stronger understanding of why things differe here
def learn(self):#First check if memory is even big enough
if self.memory.mem_cntr < self.batch_size:
returnself.q_eval.optimizer.zero_grad()#Replace target network if appropriate
self.replace_target_network()states, actions, rewards, states_, dones = self.sample_memory()
#Fetch states and advantage actions for current state using eval network
#Also fetch the same for next state using target network
V_s, A_s = self.q_eval.forward(states)
V_s_, A_s_ = self.q_next.forward(states_)
#Eval network calculation of next state V and A
V_s_eval, A_s_eval = self.q_eval.forward(states_)#Indices for matrix multiplication
indices = np.arange(self.batch_size)#Calculate current state Q-values and next state max Q-value by aggregation, subtracting constant advantage mean
#Along first dimension, which is action dimension, keeping original matrix dimensions
#recall [indices,actions] is used to maintain array shape of (batch_size) instead of (batch_size,actions)
#Essentilly by adding a a batch index to our vector array we ensure that calculated Q_pred is not tabular, but applicable for a batch update
q_pred = T.add(V_s,
(A_s - A_s.mean(dim=1, keepdim=True)))[indices, actions]
#For q_next, fetch max along the action dimension. 0th element, because max returns a tuple,
#of which 0th position are values and 1st position the indices.
q_next = T.add(V_s_,
(A_s_ - A_s_.mean(dim=1, keepdim=True)))
#QEval q-values for DDQN
q_eval = T.add(V_s_eval, (A_s_eval - A_s_eval.mean(dim=1,keepdim=True)))
max_actions = T.argmax(q_eval, dim=1)
q_next[dones] = 0.0
#Build your target using the current state reward and q_next, DDQN setup
q_target = rewards + self.gamma*q_next[indices, max_actions]loss = self.q_eval.loss(q_target, q_pred).to(self.q_eval.device)
loss.backward()
self.q_eval.optimizer.step()
self.learn_step_counter += 1self.decrement_epsilon()
定义了所有支持代码后,让我们运行主训练循环。我们已经在最初的总结中定义了大部分,但是让我们为后代回忆一下。
- 对于训练集的每一步,在使用ε-贪婪策略选择下一个动作之前,我们将输入图像堆栈输入到我们的网络中,以生成可用动作的概率分布。
- 然后,我们将它输入到网络中,获取下一个状态和相应奖励的信息,并将其存储到我们的缓冲区中。我们更新我们的堆栈,并通过一些预定义的步骤重复这一过程。
- 在一集的结尾,我们将下一个状态输入到我们的网络中,以便获得下一个动作。我们还通过对当前奖励进行贴现来计算下一个奖励。
- 我们使用去耦网络通过上述 Q 学习更新函数生成我们的目标 y 值,并训练我们的 actor 网络。
- 通过最小化训练损失,我们更新网络权重参数,以便为下一个策略输出改进的状态-动作值。
- 我们通过跟踪模型的平均得分(在 100 个训练步骤中测量)来评估模型。
# Main threadenv = make_env('DefendTheLine-v0')
best_score = -np.inf
load_checkpoint = False
n_games = 20000agent = DuelDDQNAgent(gamma=0.99, epsilon=1.0, lr=0.001,input_dims=(env.observation_space.shape),n_actions=env.action_space.n, mem_size=5000, eps_min=0.15,batch_size=32, replace=1000, eps_dec=1e-5,chkpt_dir='/content/', algo='DuelDDQNAgent',env_name='vizdoogym')if load_checkpoint:
agent.load_models()fname = agent.algo + '_' + agent.env_name + '_lr' + str(agent.lr) +'_'+ str(n_games) + 'games'
figure_file = 'plots/' + fname + '.png'n_steps = 0
scores, eps_history, steps_array = [], [], []for i in range(n_games):
done = False
observation = env.reset()score = 0
while not done:
action = agent.choose_action(observation)
observation_, reward, done, info = env.step(action)
score += rewardif not load_checkpoint:
agent.store_transition(observation, action,reward, observation_, int(done))
agent.learn()
observation = observation_
n_steps += 1scores.append(score)
steps_array.append(n_steps)avg_score = np.mean(scores[-100:])if avg_score > best_score:
best_score = avg_score
print('Checkpoint saved at episode ', i)
agent.save_models()print('Episode: ', i,'Score: ', score,' Average score: %.2f' % avg_score, 'Best average: %.2f' % best_score,'Epsilon: %.2f' % agent.epsilon, 'Steps:', n_steps)eps_history.append(agent.epsilon)
if load_checkpoint and n_steps >= 18000:
break#x = [i+1 for i in range(len(scores))]
我们绘制了 500 集和 1000 集代理人的平均得分和我们的 epsilon 值。可以观察到,DuelDDQN 模型的收敛性明显优于基线 DQN 和 DDQN 模型的收敛性,甚至稍微改善了 DuelDQN 模型的性能。
500 集后我们经纪人的奖励分配。
1000 集后我们经纪人的奖励分配。
我们可以想象我们的代理人在 500 集以下的表现。
500 集的代理性能。
回想一下,我们以前的代理遭受了陷入局部最小值的特殊问题,依赖于怪物之间的友好射击作为主要的得分策略,而不是代理的主动性。我们的 DuelDDQN 代理没有什么不同,比以前的模型更快地达到这个最小值。解决这个问题要么需要改变环境(用扫射代替转弯可能是一种选择),加速学习以避免模式崩溃(如通过动量),要么通过奖励工程——例如,根据生存的持续时间或怪物从代理人的武器中受到的原始伤害增加相应的奖励。
那么,通过我们的系列课程,我们学到了什么?
- 像 DQN 这样的价值学习架构为 RL 代理开发提供了一个很好的基线。
- 这种架构的性能可以通过对学习过程的设计进行小的修改来提高。
- 鼓励模型探索,特别是在高水平的收敛上,是学习过程的一把双刃剑。
- 就实现目标而言,一个模型的表现只能和它的回报函数的设计一样好,这必须通过观察仔细分析或推断。
这就结束了决斗双深度 Q 学习的实现,以及我们关于 DQN 的系列。在我们的下一篇文章中,我们将暂时停止强化学习,尝试一些特定的生成图像处理技术。
我们希望你喜欢这篇文章,并希望你查看 GradientCrescent 上的许多其他文章,涵盖人工智能的应用和理论方面。为了保持对 GradientCrescent 的最新更新,请考虑关注该出版物并关注我们的 Github 资源库
来源
萨顿等人。al,“强化学习”
塔博尔,“运动中的强化学习”
西蒙尼尼,“深度 Q 学习的改进*