PyTorch 深度学习实战 | DIEN 模拟兴趣演化的序列网络

Posted TiAmo zhang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyTorch 深度学习实战 | DIEN 模拟兴趣演化的序列网络相关的知识,希望对你有一定的参考价值。

01、实例:DIEN 模拟兴趣演化的序列网络

深度兴趣演化网络(Deep Interest Evolution Network,DIEN)是阿里巴巴团队在2018年推出的另一力作,比DIN 多了一个Evolution,即演化的概念。

在DIEN 模型结构上比DIN 复杂许多,但大家丝毫不用担心,我们将DIEN 拆解开来详细地说明。首先来看从DIEN 论文中截下的模型结构图,如图1所示。

■ 图1 DIEN模型结构全图

这张图初看之下很复杂,但可从简单到难一点点来说明。首先最后输出往前一段的截图如图2所示。

■ 图2 DIEN模型结构局部图(1)

这部分很简单,是一个MLP,下面一些箭头表示经过处理的向量。这些向量会经一个拼接层拼接,然后经几个全连接层,全连接层的激活函数可选择PReLU 或者Dice。最后用了一个Softmax(2)表示二分类,当然也可用Sigmoid进行二分类任务。

对输出端了解过后,再来看输入端,将输入端的部分放大后截图如图3所示。

■ 图3 DIEN模型结构局部图(2)

从右往左看,UserProfile Feature 指用户特征,Context Feature指内容特征,Target Ad指目标物品,其实这3个特征表示的无非是随机初始化一些向量,或者通过特征聚合的方式量化表达各种信息。

DIEN 模型的重点就在图3的user behavior sequence区域。user behavior sequence代表用户行为序列,通常利用用户历史交互的物品代替。图4展示了这块区域的全貌。

■ 图4 DIEN模型结构局部图(3)

这部分是DIEN 算法的核心:

第一部分: 用户行为序列,是将用户历史交互的物品序列经Embedding层初始化物品序列向量准备输入下一层,代码如下:

#recbyhand\\chapter3\\s34_DIEN.py
#初始化embedding
items = nn.Embedding( n_items, dim, max_norm = 1 )
#[batch_size, len_seqs, dim]
item_embs = items(history_seqs)#history_seqs指用户历史物品序列id

所以输出的是一个[批次样本数量,序列长度,向量维度]的张量。

第二部分: 兴趣抽取层,是一个GRU 网络,将上一层的输出在这一层输入。GRU 是RNN 的一个变种,在PyTorch里有现成模型,所以只有以下两行代码。

#recbyhand\\chapter3\\s34_DIEN.py
#初始化gru网络,注意正式写代码时,初始化动作通常写在__init__() 方法里
GRU = nn.GRU( dim, dim, batch_first=True)
outs, h = GRU(item_embs)

和RNN 网络一样,会有两个输出,一个是outs,是每个GRU 单元输出向量组成的序列,维度是[批次样本数量,序列长度,向量维度],另一个h 指的是最后一个GRU 单元的输出向量。在DIEN 模型中,目前位置处的h 并没有作用,而outs却有两个作用。一个作用是作为下一层的输入,另一个作用是获取辅助loss。

什么是辅助loss,其实DIEN 网络是一个联合训练任务,最终对目标物品的推荐预测可以产生一个损失函数,暂且称为Ltarget,而这里可以利用历史物品的标注得到一个辅助损失函数,此处称为Laux。总的损失函数的计算公式为

其中,α 是辅助损失函数的权重系数,是个超参。DIEN 给出的方法是一个二分类预测,如图5所示。

■ 图5 DIEN模型结构局部图(4)

历史物品标注指的是用户对对应位置的历史物品交互的情况,通常由1和0组成,1表示“感兴趣”,0则表示“不感兴趣”,如图5所示,将GRU 网络输出的outs与历史物品序列的Embedding输入一个二分类的预测模型中即可得到辅助损失函数,代码如下:

#recbyhand\\chapter3\\s34_DIEN.py
#辅助损失函数的计算过程
def forwardAuxiliary( self, outs, item_embs, history_labels ):
    '''
    :param item_embs: 历史序列物品的向量 [ batch_size, len_seqs, dim ]
    :param outs: 兴趣抽取层GRU网络输出的outs [ batch_size, len_seqs, dim ]
    :param history_labels: 历史序列物品标注 [ batch_size, len_seqs, 1 ]
    :return: 辅助损失函数
    '''
#[ batch_size * len_seqs, dim ]
item_embs = item_embs.reshape( -1, self.dim )
#[ batch_size * len_seqs, dim ]
    outs = outs.reshape( -1, self.dim )
#[ batch_size * len_seqs ]
    out = torch.sum( outs * item_embs, dim = 1 )
#[ batch_size * len_seqs, 1 ]
    out = torch.unsqueeze( torch.sigmoid( out ), 1 )
#[ batch_size * len_seqs,1 ]
history_labels = history_labels.reshape( -1, 1 ).float()
    return self.BCELoss( out, history_labels )

调整张量形状后做点乘,Sigmoid激活后与历史序列物品标注做二分类交叉熵损失函数(BCEloss)。

以上是第二部分兴趣抽取层所做的事情,最后来看最关键的第三部分。

第三部分: 兴趣演化层,主要由一个叫作AUGRU 的网络组成,AUGRU 是在GRU 的基础上增加了注意力机制。全称叫作GRU With Attentional Update Gate。AUGRU 的细节结构如图6所示。

■ 图6 AUGRU 单元细节

02、图书推荐

在大数据时代背景下,统计学作为数据分析领域的基础,被应用于各行各业,其方法发挥着重要作用。为了更广泛地普及统计学知识,培养更多的统计学人才,本书应运而生。

本书融合大量情景案例,轻松理解统计知识;零基础起步商务统计,培养数据价值思维。入门级统计学教程,培养数据价值思维。

作为入门级图书,本书内容安排如下。第1章从不确定性出发,讲述统计学和不确定性的关系,以及统计学中用于描述不确定性的各种概率模型。第2章是参数估计,系统讲述统计学中矩估计和极大似然估计两种常用的参数估计方法,并基于两种方法介绍各种常见概率分布中参数的点估计和区间估计。第3章是假设检验,首先从不确定性的角度探讨实际中的各种决策问题,帮助读者理解假设检验的思想和应用场景,然后系统介绍假设检验的方法论及各种常见推广。第4章是回归分析,首先介绍回归分析的思想和广泛的应用场景,然后系统地介绍各类常用模型,从线性回归到广义线性回归,最终落脚到两种机器学习算法(决策树、神经网络)。

本书特别强调实际应用,因此各个章节都辅以大量的实际案例,在介绍统计学基础知识的同时培养读者使用统计学方法解决实际问题的能力。

PyTorch强化学习实战——强化学习环境配置与PyTorch基础

PyTorch强化学习实战(1)——强化学习环境配置与PyTorch基础

0. 前言

工欲善其事,必先利其器。为了更专注于学习强化学习的思想,而不必关注其底层的计算细节,我们首先搭建相关深度学习环境,主要包括 Python 以及 PyTorchPython,更具体说使用 Python3.7+ 作为我们实现强化学习算法进行实战的编程语言。PyTorch 是我们将要使用的主要深度学习框架,PyTorchFacebook AI Research 基于 Torch 开发,是主流的机器学习库之一。在 PyTorch 中,使用张量 (Tensors) 代替了 NumPy 中的 ndarray 数据类型,从而提供了更强大的灵活性和与 GPU 的兼容性。由于其强大的计算图特性和简单的上手难度,使得 PyTorch 受到了广泛的欢迎,并且它已被越来越多的科技巨头所采用。

1. 搭建 PyTorch 环境

关于 Python 的安装和配置,在此不再赘述。可选的,由于,深度学习中模型的训练需要大量时间,因此通常使用 GPU加速计算,在安装 PyTorch 之前需要根据选用的 PyTorch 版本和显卡安装 CUDAcudnn,关于 CUDAcudnn 的安装和配置可以参考官方文档,建议在安装之前根据自己的操作系统认真查看官方的安装文档,可以避免踩不必要的坑。
然后,在 PyTorch 官方网页,根据自己实际的环境,进行相应的选择,在 Run this Command 栏中将给出安装 PyTorch 的命令:

在此,我们以 LinuxpipPythonCUDA10.2 为例,复制并在终端执行安装命令:

pip3 install torch torchvision torchaudio

为了确认 PyTorch 已正确安装,可以在 Python shell 中运行以下代码:

>>> import torch
>>> test = torch.empty(2,2)
>>> print(test)
tensor([[2.9685e-26, 4.5722e-41],
        [2.9685e-26, 4.5722e-41]])

如果能够正确调用 PyTorch 相关函数,表明 PyTorch 已正确安装。需要注意的是,以上代码中,使用 torch.emty() 中创建了一个尺寸为 2 x 2 的张量,它是一个空矩阵,这里的“空”并不意味着所有元素的值都为 Null,而是使用一些被认为是占位符的无意义浮点数,需要在之后进行赋值,这与 NumPy 中的空数组类似。

2. OpenAI Gym简介与安装

深度学习环境配置后,我们继续安装 OpenAI GymOpenAI Gym 提供多种环境,可以在其中开发和比较强化学习算法,通过提供标准 API 用于学习算法和环境之间的通信。Gym 事实上已经成为用于强化学习中的基准,涵盖了许多功能全面且易于使用的环境。这类似于我们经常在监督学习中用作基准的数据集,例如 MNISTImagenet,和 Reuters 新闻数据集。
OpenAI 是一家致力于建立安全的人工智能并确保其对人类有益的非营利性研究公司。OpenAI Gym 是一个功能强大的开源工具包,可用于各种强化学习模拟和任务,包括从赛车到 Atari 游戏等多种类型,Gym 提供的完整环境列表可以参见官方网页。我们可以使用任何机器学习库,包括 PyTorchTensorFlowKeras 等,训练智能体与 OpenAI Gym 环境进行交互。
Gym 的安装与其它标准第三方库一样,可以通过 pip 命令方便的安装:

pip install gym

安装完成后,可以在命令行中使用以下代码来查看可用的 gym 环境:

>>> import gym
>>> print(gym.envs.registry.all())
dict_values([EnvSpec(Copy-v0), EnvSpec(RepeatCopy-v0), EnvSpec(ReversedAddition-v0), EnvSpec(ReversedAddition3-v0), EnvSpec(DuplicatedInput-v0), EnvSpec(Reverse-v0), EnvSpec(CartPole-v0), EnvSpec(CartPole-v1), EnvSpec(MountainCar-v0), EnvSpec(MountainCarContinuous-v0), EnvSpec(Pendulum-v0),...

可以看到,如果正确安装了 Gym,使用 gym.envs.registry.all() 可以返回 Gym 中所有可用的环境。

3. 模拟 Atari 环境

Atari 环境中包含各种 Atari 电子游戏,例如 AlienAirRaidPongSpace Race 等。接下来,我们按照以下步骤安装模拟 Atari 环境,要运行 Atari 环境,需要在终端中运行以下命令安装 Atari 依赖项:

pip install gym[atari]

安装 Atari 依赖项后,我们可以使用 Python 导入 Gym 库来验证安装是否成功:

>>> import gym

使用 Gym,可以通过使用环境名称作为参数调用 make() 方法创建环境实例。以创建 SpaceInvaders 环境的实例为例:

>>> env = gym.make('SpaceInvaders-v0')

重置 SpaceInvaders 环境,可以看到 reset() 方法返回环境中的初始状态:

>>> env.reset()
array([[[ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0],
        ...,
        [ 0,  0,  0],
        [ 0,  0,  0],
        [ 0,  0,  0]],
       ...,
       [[80, 89, 22],
        [80, 89, 22],
        [80, 89, 22],
        ...,
        [80, 89, 22],
        [80, 89, 22],
        [80, 89, 22]]], dtype=uint8)

使用 render 方法渲染 SpaceInvaders 环境,可以看到弹出的游戏窗口:

>>> env.render()
True

在以上游戏窗口中可以看到,其中包含三个红色太空飞船,表示飞船包含三条命。使用 sample() 方法可以随机选择智能体执行的动作,step() 方法使智能体根据给定参数执行相应动作。通常,在强化学习中,我们将使用复杂的智能体选择要执行的动作。在这里,仅仅为了演示如何模拟环境,以及智能体如何采取行动而不管结果如何。通过随机选择动作并执行所选择的动作:

>>> action = env.action_space.sample()
>>> new_state, reward, is_done, info = env.step(action)

step() 方法可以返回执行动作后的情况,包括以下内容:

  • new_state:对游戏环境新的观测值 (observation),在单智能体环境中也可以理解为新状态
  • reward:执行动作后在新状态下得到的相应奖励
  • is_done:用于指示游戏是否结束的标志,例如,在 SpaceInvaders 环境中,如果飞船三条命耗尽或所有外星敌人都被消失,则为 True,否则,为False
  • info:与环境有关的其他信息,例如,在 SpaceInvaders 环境中,info 中包含当前状态下飞船剩余生命的数量,info 通常用于程序的调试

可以通过 action_sapce 查看智能体能够执行的动作数量,即动作空间大小:

>>> print(env.action_space)
Discrete(6)

SpaceInvaders 环境中,从 05 的动作分别代表不进行任何操作,开火,向上,向右,向左和向下,这是飞船在游戏中可以做的所有动作。
对环境的观察值 new_state 是一个形状为 210 x 160 x 3 的矩阵,即游戏屏幕的每一帧都是大小为 210 x 160RGB 图像:

>>> print(new_state.shape)
(210, 160, 3)

接下来,我们打印 is_doneinfo 查看其具体内容:

>>> print(is_done)
False
>>> print(info)
'ale.lives': 3

然后,继续渲染环境,render() 方法根据对环境的最新观察值来更新显示窗口,可以看到游戏窗口中的内容有所改变,但由于飞船只做了一个动作,因此改变并不明显:

>>> env.render()

为了使智能体执行更多动作,我们接下来使用 while 循环:

>>> while not is_done:
...     action = env.action_space.sample()
...     new_state, reward, is_done, info = env.step(action)
...     env.render()
...     print(info)
... 
True
'ale.lives': 3
True
'ale.lives': 3
True
...
'ale.lives': 1
True
'ale.lives': 0

可以看到游戏的运行情况,飞船和外星人都会不断移动和射击。最后,在游戏结束时,窗口如下所示,在这场游戏中获取 30 分,并且最终的生命值为 0

额外安装 Atari 依赖项的原因是由于其并不包含在 gym 环境中,此外 Box2dClassic controlMuJoCoRobotics 等环境也并不在 gym 环境中,这些环境的安装与 Atari 的安装类似,以 Box2d 环境为例,在首次运行环境之前安装 Box2d 依赖项:

pip install gym[box2d]

安装完成后,我们就可以使用 LunarLander 环境,如下所示:

>>> env = gym.make('LunarLander-v2')
>>> env.reset()
array([-1.2830734e-03,  1.4214842e+00, -1.2998608e-01,  4.6951649e-01,
        1.4936496e-03,  2.9443752e-02,  0.0000000e+00,  0.0000000e+00],
      dtype=float32)
>>> env.render()
True

如果并不确定要模拟环境在 make() 方法中应使用的名称,可以在官方环境列表中进行查找。该表除了给出用于调用环境的名称外,还列出了游戏窗口画面矩阵的尺寸和可以采取的动作数量。

4. 模拟 CartPole 环境

为了加深对 gym 环境的了解,本节我们将模拟另一个强化学习中的经典环境—— CartPole
CartPole 是一项经典的强化学习任务,在 CartPole 环境中,一根直立的杆置于推车的顶部。智能体在一个时间步中会将推车向左或向右移动 1 个单位,其目的是平衡推车顶部的杆并防止其跌落。如果杆与垂直方向的夹角超过 12 度或者推车离原点的距离超过 2.4 个单位,则我们认为杆已跌落。遇到以下任何情况之一,我们可以认为一个回合/情节 (episode) 终止:

  • 推车上的杆跌落
  • 时间步数达到 200

接下来,我们在 gym 中模拟 CartPole 环境,为了对 CartPole 环境有所了解,我们首先导入 gym 库,实例化 CartPole-v0 环境,并打印其状态空间和动作空间:

>>> import gym
>>> env = gym.make('CartPole-v0')
>>> print(env.observation_space)
Box(4,)
>>> print(env.action_space)
Discrete(2)

可以看到 CartPole-v0 环境的状态空间以 4 维数组表示,并且有 2 个可能的动作。
接下来,重置环境,将返回 4 个浮点数数组表示的初始状态:

>>> env.reset()
array([ 0.0461389 , -0.0080376 ,  0.03944101,  0.03537847])

渲染环境,可以看出弹出的游戏窗口:

>>> env.render()
True

接下来,我们使用 while 循环,使智能体执行尽可能多的随机动作:

>>> is_done = False
>>> while not is_done:
...     action = env.action_space.sample()
...     new_state, reward, is_done, info = env.step(action)
...     env.render()
...     print(new_state)
... 
True
[ 0.04970809 -0.0091745   0.03525649  0.06046739]
True
[ 0.0495246  -0.20478375  0.03646584  0.36406219]
......
True
[-0.0470163  -1.19033894  0.21028888  2.08433865]

我们在每个时间步中打印出当前状态,数组中的包含 4 个浮点数,可以在 Gym GitHub Wiki 页面上找到关于这 4 个浮点数的更多信息,这 4 个浮点数分别表示:

  • 推车位置:范围在区间 [-2.4, 2.4] 之内,任何超出此范围的位置都会导致回合终止
  • 推车速度
  • 杆与垂直方向夹角:小于 -0.209 (-12 度)或大于 0.209 (12 度)的值将导致回合终止
  • 杆端点处的速度

动作的可能值为 01,分别对应于将推车向左和向右推动。在回合终止之前的每个时间步中,智能体都能从环境中得到奖励 +1,因此,总奖励等于一个回合中的时间步总数。
在代码执行过程中,可以看到推车和杆都会移动,并最终停止,停止时游戏窗口如下所示:

由于向左或向右的动作是随机选择的,因此一个回合仅持续几个时间步。我们也可以记录整个过程,以便进行回顾。为了录制视频记录游戏过程,我们首先需要安装 ffmpeg,对于 Linux,可以使用以下命令进行安装,对于其他操作系统,可以参考官方文档

sudo apt-get install ffmpeg

为了进行记录,需要创建 CartPole 实例后,使用 Monitor 对象记录游戏窗口中显示的内容并将其存储在指定目录中,其余的步骤和以上过程完全一致:

# monitor_gym.py
import gym

env = gym.make('CartPole-v0')
env.reset()
video_dir = './cartpole_video'
env = gym.wrappers.Monitor(env, video_dir)
is_done = False
while not is_done:
    action = env.action_space.sample()
    new_state, reward, is_done, info = env.step(action)
    env.render()

运行以上代码,回合终止后,我们可以看到在 video_dir 文件夹中包含了一个 .mp4 文件,其中记录了一个回合的游戏过程。

为了评估智能体的表现,我们可以模拟多个回合,计算每个回合的总奖励,然后计算平均值。平均总奖励可以用于评估采取随机动作的智能体性能,我们使用 10000 个回合,在每个回合中,我们通过累积每个时间步中的奖励来计算总奖励:

# multi_episodes.py
import gym
env = gym.make('CartPole-v0')
n_episode = 10000
total_rewards = []

for e in range(n_episode):
    state = env.reset()
    total_reward = 0
    is_done = False
    while not is_done:
        action = env.action_space.sample()
        state, reward, is_done, info = env.step(action)
        total_reward += reward
    total_rewards.append(total_reward)
# 最后,我们计算平均总奖励
print('Average total reward over  episodes: '.format(n_episode, sum(total_rewards) / n_episode))

运行以上代码,可以看到以下输出:

Average total reward over 10000 episodes: 22.1942

可以看出,采取随机动作的平均总奖励为 22.25,采取随机动作过于简单,我们将在之后的学习中实现更加有效的算法策略。

5. PyTorch 基础

我们将使用 PyTorch 作为实现强化学习算法的数值计算库。PyTorchFacebook 开发的科学计算和机器学习库,张量 (Tensor) 是 PyTorch 中的核心数据结构,类似于 NumPy 中的 ndarray。 但是,PyTorch 在数组操作和遍历等方面的速度比 NumPy 更快,这主要是由于在 PyTorch 中,数组元素的访问速度更快。接下来,我们介绍一些 PyTorch 中的基本知识,以便在之后的强化学习实战中使用。
默认张量 (Tensor) 的数据类型是 torch.float32,这是张量运算最常用的数据类型。从区间 [0,1] 的均匀分布中随机采样生成浮点数:

>>> import torch
>>> x = torch.rand(4,3)
>>> print(x)
tensor([[0.1968, 0.6183, 0.9863],
        [0.6465, 0.5472, 0.7393],
        [0.4933, 0.1890, 0.0459],
        [0.4462, 0.8802, 0.4948]])

我们也可以创建指定数据类型的张量,例如,返回一个 double 类型的张量 (float64),如下所示:

>>> x = torch.rand(4,3,dtype=torch.double)
>>> print(x)
tensor([[0.3899, 0.2509, 0.5130],
        [0.5605, 0.5399, 0.2442],
        [0.5419, 0.8523, 0.3360],
        [0.4898, 0.1152, 0.4379]], dtype=torch.float64)

创建指定尺寸的矩阵,并用 01 进行填充:

>>> x = torch.zeros(4,3)
>>> print(x)
tensor([[0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.],
        [0., 0., 0.]])
>>> x = torch.ones(4,3)
>>> print(x)
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]])

可以使用数据的 size 方法获得张量的尺寸,其返回 torch.Size 元组:

>>> print(x.size())
torch.Size([4, 3])

可以使用 view 方法变换张量形状:

>>> x_reshaped = x.view(2,6)
>>> print(x_reshaped)
tensor([[1., 1., 1., 1., 1., 1.],
        [1., 1., 1., 1., 1., 1.]])

可以直接使用数据创建张量,包括单个值,列表和嵌套列表:

>>> x_1 = torch.tensor(4)
>>> print(x_1)
tensor(4)
>>> x_2 = torch.tensor([11.11,22,33,0])
>>> print(x_2)
tensor([11.1100, 22.0000, 33.0000,  0.0000])
>>> x_3 = torch.tensor([[1,2,3,4],[5,6.6,7.7,8.0]])
>>> print(x_3)
tensor([[1.0000, 2.0000, 3.0000, 4.0000],
        [5.0000, 6.6000, 7.7000, 8.0000]])

可以使用类似于 NumPy 的方式利用索引来访问张量中的元素,对于单元素张量,我们也可以使用 item() 方法来访张量中的元素:

>>> print(x_3[0])
tensor([1., 2., 3., 4.])
>>> print(x_3[0,0])
tensor(1.)
>>> print(x_3[:,2])
tensor([3.0000, 7.7000])
>>> print(x_3[:,2:])
tensor([[3.0000, 4.0000],
        [7.7000, 8.0000]])
>>> print(x_1.item())
4

TensorNumPy 数组可以相互转换,使用 numpy() 方法可以将张量转换为 NumPy 数组,而想要将 NumPy 数组转换为张量想要使用 from_numpy() 方法:

>>> print(x_3.numpy())
[[1.  2.  3.  以上是关于PyTorch 深度学习实战 | DIEN 模拟兴趣演化的序列网络的主要内容,如果未能解决你的问题,请参考以下文章

1914DIEN:深度兴趣演化⽹络

对比学习:《深度学习之Pytorch》《PyTorch深度学习实战》+代码

深度学习-PyTorch框架实战系列

深度学习理论与实战PyTorch实现

分享《深度学习之Pytorch(廖星宇著)》+《PyTorch深度学习实战(侯宜军 著)》+源代码

分享《深度学习之Pytorch(廖星宇著)》+《PyTorch深度学习实战(侯宜军 著)》+源代码