一. 李沐循环神经网络RNN原理

1.介绍

前面博文介绍了nnn元语法模型,其中单词xtx_txt​在时间步ttt的条件概率仅取决于前面n−1n-1n−1个单词。对于时间步t−(n−1)t-(n-1)t−(n−1)之前的单词,如果想将其可能产生的影响合并到xtx_txt​上,需要增加nnn,导致模型参数的数量也会随之呈指数增长,因为词表V\mathcal{V}V需要存储∣V∣n|\mathcal{V}|^n∣V∣n个数字,因此与其将P(xt∣xt−1,…,xt−n+1)P(x_t \mid x_{t-1}, \ldots, x_{t-n+1})P(xt​∣xt−1​,…,xt−n+1​)模型化,不如使用隐变量模型:
P(xt∣xt−1,…,x1)≈P(xt∣ht−1),P(x_t \mid x_{t-1}, \ldots, x_1) \approx P(x_t \mid h_{t-1}),P(xt​∣xt−1​,…,x1​)≈P(xt​∣ht−1​),
其中ht−1h_{t-1}ht−1​是隐状态(hidden state),也称为隐藏变量(hidden variable),它存储了到时间步t−1t-1t−1的序列信息。通常可以基于当前输入xtx_{t}xt​和先前隐状态ht−1h_{t-1}ht−1​来计算时间步ttt处的任何时间的隐状态:
ht=f(xt,ht−1).h_t = f(x_{t}, h_{t-1}).ht​=f(xt​,ht−1​).
对于上面公式中函数fff,隐变量模型不是近似值,因为hth_tht​是可以存储到目前为止观察到的所有数据,然而这样的操作可能会使计算和存储的代价都变得昂贵

注意隐藏层和隐状态指的是两个截然不同的概念,隐藏层是从输入到输出的路径上(以观测角度来理解)的隐藏的层,而隐状态则是在给定步骤所做的任何事情(以技术角度来定义)的输入,并且这些状态只能通过先前时间步的数据来计算,因此* 循环神经网络*(recurrent neural networks,RNNs)是具有隐状态的神经网络。下面看一下有隐状态的和无状态的神经网络。

1.1 无隐状态的神经网络(多层感知机)

单隐藏层的多层感知机,设隐藏层的激活函数为ϕ\phiϕ,给定一个小批量样本X∈Rn×d\mathbf{X} \in \mathbb{R}^{n \times d}X∈Rn×d,其中批量大小为nnn,输入维度为ddd,则隐藏层的输出H∈Rn×h\mathbf{H} \in \mathbb{R}^{n \times h}H∈Rn×h通过下式计算:
H=ϕ(XWxh+bh).\mathbf{H} = \phi(\mathbf{X} \mathbf{W}_{xh} + \mathbf{b}_h).H=ϕ(XWxh​+bh​).

在上面公式中看出单隐藏层网络中拥有的隐藏层权重参数为Wxh∈Rd×h\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}Wxh​∈Rd×h,
偏置参数为bh∈R1×h\mathbf{b}_h \in \mathbb{R}^{1 \times h}bh​∈R1×h,以及隐藏单元的数目为hhh,因此求和时可以应用广播机制,接下来将隐藏变量H\mathbf{H}H用作输出层的输入,输出层由下式给出:
O=HWhq+bq,\mathbf{O} = \mathbf{H} \mathbf{W}_{hq} + \mathbf{b}_q,O=HWhq​+bq​,

其中,O∈Rn×q\mathbf{O} \in \mathbb{R}^{n \times q}O∈Rn×q是输出变量,Whq∈Rh×q\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}Whq​∈Rh×q是权重参数,bq∈R1×q\mathbf{b}_q \in \mathbb{R}^{1 \times q}bq​∈R1×q是输出层的偏置参数。
如果是分类问题,可以用softmax(O)\text{softmax}(\mathbf{O})softmax(O)来计算输出类别的概率分布。只要可以随机选择“特征-标签”对,并且通过自动微分和随机梯度下降能够学习网络参数就可以。

1.2 有隐状态的循环神经网络

假设在时间步ttt有小批量输入Xt∈Rn×d\mathbf{X}_t \in \mathbb{R}^{n \times d}Xt​∈Rn×d。换言之,对于nnn个序列样本的小批量,Xt\mathbf{X}_tXt​的每一行对应于来自该序列的时间步ttt处的一个样本。接下来用Ht∈Rn×h\mathbf{H}_t \in \mathbb{R}^{n \times h}Ht​∈Rn×h表示时间步ttt的隐藏变量,与多层感知机不同的是,在这里保存了前一个时间步的隐藏变量Ht−1\mathbf{H}_{t-1}Ht−1​,并引入了一个新的权重参数Whh∈Rh×h\mathbf{W}_{hh} \in \mathbb{R}^{h \times h}Whh​∈Rh×h,来描述如何在当前时间步中使用前一个时间步的隐藏变量。具体地说,当前时间步隐藏变量由当前时间步的输入与前一个时间步的隐藏变量一起计算得出:
Ht=ϕ(XtWxh+Ht−1Whh+bh).\mathbf{H}_t = \phi(\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh} + \mathbf{b}_h).Ht​=ϕ(Xt​Wxh​+Ht−1​Whh​+bh​).

与 无状态的神经网络相比,有状态的循环神经网络多添加了一项Ht−1Whh\mathbf{H}_{t-1} \mathbf{W}_{hh}Ht−1​Whh​。从相邻时间步的隐藏变量Ht\mathbf{H}_tHt​和Ht−1\mathbf{H}_{t-1}Ht−1​之间的关系可知,这些变量捕获并保留了序列直到其当前时间步的历史信息,就如当前时间步下神经网络的状态或记忆,因此这样的隐藏变量被称为隐状态(hidden state)由于在当前时间步中,隐状态使用的定义与前一个时间步中使用的定义相同,因此 循环神经网络隐状态的计算是循环的(recurrent),因此基于循环计算的隐状态神经网络被命名为循环神经网络(recurrent neural network)。在循环神经网络中执行隐状态计算的层称为循环层(recurrent layer)
对于时间步ttt,输出层的输出类似于多层感知机中的计算:

Ot=HtWhq+bq.\mathbf{O}_t = \mathbf{H}_t \mathbf{W}_{hq} + \mathbf{b}_q.Ot​=Ht​Whq​+bq​.

循环神经网络的参数包括隐藏层的权重Wxh∈Rd×h,Whh∈Rh×h\mathbf{W}_{xh} \in \mathbb{R}^{d \times h}, \mathbf{W}_{hh} \in \mathbb{R}^{h \times h}Wxh​∈Rd×h,Whh​∈Rh×h和偏置bh∈R1×h\mathbf{b}_h \in \mathbb{R}^{1 \times h}bh​∈R1×h,以及输出层的权重Whq∈Rh×q\mathbf{W}_{hq} \in \mathbb{R}^{h \times q}Whq​∈Rh×q和偏置bq∈R1×q\mathbf{b}_q \in \mathbb{R}^{1 \times q}bq​∈R1×q,注意即使在不同的时间步,循环神经网络也总是使用这些模型参数,因此循环神经网络的参数开销不会随着时间步的增加而增加。
在任意时间步ttt,隐状态和当前时间步网络的输出的计算可以被看作,下图展示了循环神经网络在三个相邻时间步的计算逻辑。:

  1. 拼接当前时间步ttt的输入Xt\mathbf{X}_tXt​和前一时间步t−1t-1t−1的隐状态Ht−1\mathbf{H}_{t-1}Ht−1​,模型参数是Wxh\mathbf{W}_{xh}Wxh​和Whh\mathbf{W}_{hh}Whh​的拼接,以及bh\mathbf{b}_hbh​的偏置
  2. 将拼接的结果送入带有激活函数ϕ\phiϕ的全连接层。
    全连接层的输出是当前时间步ttt的隐状态Ht\mathbf{H}_tHt​
  3. 当前时间步ttt的隐状态Ht\mathbf{H}_tHt​将参与计算下一时间步t+1t+1t+1的隐状态Ht+1\mathbf{H}_{t+1}Ht+1​。而且Ht\mathbf{H}_tHt​还将送入全连接输出层,用于计算当前时间步ttt的输出Ot\mathbf{O}_tOt​

注意上面提到,隐状态中XtWxh+Ht−1Whh\mathbf{X}_t \mathbf{W}_{xh} + \mathbf{H}_{t-1} \mathbf{W}_{hh}Xt​Wxh​+Ht−1​Whh​的计算,相当于Xt\mathbf{X}_tXt​和Ht−1\mathbf{H}_{t-1}Ht−1​的拼接与Wxh\mathbf{W}_{xh}Wxh​和Whh\mathbf{W}_{hh}Whh​的拼接的矩阵乘法。虽然这个性质可以通过数学证明,但在下面使用一个简单的代码来说明一下。首先,定义矩阵X、W_xh、H和W_hh,它们的形状分别为(3,1)(3,1)(3,1)、(1,4)(1,4)(1,4)、(3,4)(3,4)(3,4)和(4,4)(4,4)(4,4)。分别将X乘以W_xh,将H乘以W_hh,然后将这两个乘法相加,得到一个形状为(3,4)(3,4)(3,4)的矩阵。

import torch
from d2l import torch as d2l
X, W_xh = torch.normal(0, 1, (3, 1)), torch.normal(0, 1, (1, 4))
H, W_hh = torch.normal(0, 1, (3, 4)), torch.normal(0, 1, (4, 4))
torch.matmul(X, W_xh) + torch.matmul(H, W_hh)
'''
输出结果如下:
tensor([[-0.5702, -0.4673, -0.1508, -0.0185],[-1.3559,  0.8042, -1.4394, -3.1735],[ 0.6559,  1.3140,  0.2949,  0.0781]])
'''

现在沿列(轴1)拼接矩阵X和H, 沿行(轴0)拼接矩阵W_xh和W_hh,这两个拼接分别产生形状 (3,5) 和形状 (5,4) 的矩阵,再将这两个拼接的矩阵相乘, 得到与上面相同形状 (3,4) 的输出矩阵。

torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0))
'''
输出结果如下:
tensor([[-0.5702, -0.4673, -0.1508, -0.0185],[-1.3559,  0.8042, -1.4394, -3.1735],[ 0.6559,  1.3140,  0.2949,  0.0781]])
'''

2.基于循环神经网络的字符级语言模型

我们的目标是根据过去的和当前的词元预测下一个词元,因此我们将原始序列移位一个词元作为标签。接下来使用循环神经网络来构建语言模型:设小批量大小为1,批量中的那个文本序列为“machine”,为了简化后续部分的训练,考虑使用字符级语言模型(character-level language model),将文本词元化为字符而不是单词。 下图展示了如何通过基于字符级语言建模的循环神经网络,使用当前的和先前的字符预测下一个字符。

在训练过程中,对每个时间步的输出层的输出进行softmax操作,然后利用交叉熵损失计算模型输出和标签之间的误差。由于隐藏层中隐状态的循环计算, 上图中的第333个时间步的输出O3\mathbf{O}_3O3​由文本序列“m”、“a”和“c”确定。由于训练数据中这个文本序列的下一个字符是“h”,因此下一个字符生成是基于特征序列“m”、“a”、“c”和这个时间步的标签“h”生成的。
在实践中,使用的批量大小为n>1n>1n>1,每个词元都由一个ddd维向量表示。
因此,在时间步ttt输入Xt\mathbf X_tXt​将是一个n×dn\times dn×d矩阵。模型输入形状为(时间步数num_steps,批量大小batch_size,词元表示维数vocab_size)。

3. 困惑度(Perplexity)

困惑度用于度量语言模型的质量,用于评估基于循环神经网络的模型。一个好的语言模型能够用高度准确的词元来预测我们接下来会看到什么。
考虑一下由不同的语言模型给出的对“It is raining …”(“下雨了…”)的续写:

  1. “It is raining outside”(外面下雨了)
  2. “It is raining banana tree”(香蕉树下雨了)
  3. “It is raining piouw;kcj pwepoiut”(piouw;kcj pwepoiut下雨了)

就质量而言,例111显然是最合乎情理、在逻辑上最连贯的,虽然这个模型可能没有很准确地反映出后续词的语义,比如,“It is raining in San Francisco”(旧金山下雨了)和“It is raining in winter”(冬天下雨了)可能才是更完美的合理扩展,但该模型已经能够捕捉到跟在后面的是哪类单词。例222则要糟糕得多,因为其产生了一个无意义的续写。尽管如此,至少该模型已经学会了如何拼写单词,以及单词之间的某种程度的相关性。最后,例333表明了训练不足的模型是无法正确地拟合数据的。我们可以通过计算序列的似然概率来度量模型的质量。然而这是一个难以理解、难以比较的数字,毕竟较短的序列比较长的序列更有可能出现,因此评估模型产生托尔斯泰的巨著《战争与和平》的可能性不可避免地会比产生圣埃克苏佩里的中篇小说《小王子》可能性要小得多。
下面通过一个序列中所有的nnn个词元的交叉熵损失的平均值来衡量:
1n∑t=1n−log⁡P(xt∣xt−1,…,x1),\frac{1}{n} \sum_{t=1}^n -\log P(x_t \mid x_{t-1}, \ldots, x_1),n1​t=1∑n​−logP(xt​∣xt−1​,…,x1​),
其中PPP由语言模型给出,xtx_txt​是在时间步ttt从该序列中观察到的实际词元
,这使得不同长度的文档的性能具有了可比性。由于历史原因,自然语言处理的科学家更喜欢使用一个叫做困惑度(perplexity)的量。简而言之,它是 上面公式的指数:
exp⁡(−1n∑t=1nlog⁡P(xt∣xt−1,…,x1)).\exp\left(-\frac{1}{n} \sum_{t=1}^n \log P(x_t \mid x_{t-1}, \ldots, x_1)\right).exp(−n1​t=1∑n​logP(xt​∣xt−1​,…,x1​)).
困惑度的最好的理解是“下一个词元的实际选择数的调和平均数”,也即是根据困惑度大小来看预测的候选词元有几个,比如困惑度为1表示预测的候选词元只有一个,也表示网络的准确度也是最好的,如果困惑度为2表示预测的候选词元有两个,最坏的情况下困惑度为无穷大时,表示预测的候选词元有无穷个,因此网络准确度是最坏的,比如下面的一些情况:

  • 在最好的情况下,模型总是完美地估计标签词元的概率为1,在这种情况下,模型的困惑度为1。
  • 在最坏的情况下,模型总是预测标签词元的概率为0,在这种情况下,困惑度是正无穷大。
  • 在基线上,该模型的预测是词表的所有可用词元上的均匀分布,在这种情况下,困惑度等于词表中唯一词元的数量。事实上,如果在没有任何压缩的情况下存储序列,这将是我们能做的最好的编码方式,因此这种方式提供了一个重要的上限,而任何实际模型都必须超越这个上限。

4. 小结

  • 对隐状态使用循环计算的神经网络称为循环神经网络(RNN)
  • 循环神经网络的隐状态可以捕获直到当前时间步序列的历史信息。
  • 循环神经网络模型的参数数量不会随着时间步的增加而增加。
  • 使用循环神经网络创建字符级语言模型。
  • 使用困惑度来评价语言模型的质量

二. 李宏毅机器学习对RNN讲解

RNN,或者说最常用的LSTM,一般用于记住之前的状态,以供后续神经网络的判断,它由input gate、forget gate、output gate和cell memory组成,每个LSTM本质上就是一个neuron,特殊之处在于有4个输入:zzz和三门控制信号ziz_izi​、zfz_fzf​和zoz_ozo​,每个时间点的输入都是由当前输入值+上一个时间点的输出值+上一个时间点cell值来组成

1. Introduction

Slot Filling

在智能客服、智能订票系统中,往往会需要slot filling技术,它会分析用户说出的语句,将时间、地址等有效的关键词填到对应的槽上,并过滤掉无效的词语。
词汇要转化成vector,可以使用1-of-N编码,word hashing或者是word vector等方式,此外我们可以尝试使用Feedforward Neural Network来分析词汇,判断出它是属于时间或是目的地的概率

但这样做会有一个问题,该神经网络会先处理“arrive”和“leave”这两个词汇,然后再处理“Taipei”,这时对NN来说,输入是相同的,它没有办法区分出“Taipei”是出发地还是目的地

这个时候我们就希望神经网络是有记忆的,如果NN在看到“Taipei”的时候,还能记住之前已经看过的“arrive”或是“leave”,就可以根据上下文得到正确的答案,如下图所示。

这种有记忆力的神经网络,就叫做Recurrent Neural Network(RNN)

在RNN中,hidden layer每次产生的output a1a_1a1​、a2a_2a2​,都会被存到memory里,下一次有input的时候,这些neuron就不仅会考虑新输入的x1x_1x1​、x2x_2x2​,还会考虑存放在memory中的a1a_1a1​、a2a_2a2​

注:在input之前,要先给内存里的aia_iai​赋初始值,比如0

注意到,每次NN的输出都要考虑memory中存储的临时值,而不同的输入产生的临时值也尽不相同,因此改变输入序列的顺序会导致最终输出结果的改变(Changing the sequence order will change the output)

2. Slot Filling with RNN

用RNN处理Slot Filling的流程举例如下:

  • “arrive”的vector作为x1x^1x1输入RNN,通过hidden layer生成a1a^1a1,再根据a1a^1a1生成y1y^1y1,表示“arrive”属于每个slot的概率,其中a1a^1a1会被存储到memory中
  • “Taipei”的vector作为x2x^2x2输入RNN,此时hidden layer同时考虑x2x^2x2和存放在memory中的a1a^1a1,生成a2a^2a2,再根据a2a^2a2生成y2y^2y2,表示“Taipei”属于某个slot的概率,此时再把a2a^2a2存到memory中
  • 依次类推

注意:上图为同一个RNN在三个不同时间点被分别使用了三次,并非是三个不同的NN

这个时候,即使输入同样是“Taipei”,我们依旧可以根据前文的“leave”或“arrive”来得到不一样的输出

3. Elman Network & Jordan Network

RNN有不同的变形:

  • Elman Network:将hidden layer的输出保存在memory里
  • Jordan Network:将整个neural network的输出保存在memory里

由于hidden layer没有明确的训练目标,而整个NN具有明确的目标,因此Jordan Network的表现会更好一些

4. Bidirectional RNN

RNN 还可以是双向的,你可以同时训练一对正向和反向的RNN,把它们对应的hidden layer xtx^txt拿出来,都接给一个output layer,得到最后的yty^tyt

使用Bi-RNN的好处是,NN在产生输出的时候,它能够看到的范围是比较广的,RNN在产生yt+1y^{t+1}yt+1的时候,它不只看了从句首x1x^1x1开始到xt+1x^{t+1}xt+1的输入,还看了从句尾xnx^nxn一直到xt+1x^{t+1}xt+1的输入,这就相当于RNN在看了整个句子之后,才决定每个词汇具体要被分配到哪一个槽中,这会比只看句子的前一半要更好

三. 链接

循环神经网络RNN第一篇:李沐动手学深度学习V2-NLP序列模型和代码实现
循环神经网络RNN第二篇:李沐动手学深度学习V2-NLP文本预处理和代码实现
循环神经网络RNN第三篇:李沐动手学深度学习V2-NLP语言模型、数据集加载和数据迭代器实现以及代码实现
循环神经网络RNN第四篇:李沐动手学深度学习V2-RNN原理
循环神经网络RNN第五篇:李沐动手学深度学习V2-RNN循环神经网络从零实现
循环神经网络RNN第六篇:李沐动手学深度学习V2-使用Pytorch框架实现RNN循环神经网络
循环神经网络GRU第七篇:李沐动手学深度学习V2-GRU门控循环单元以及代码实现
循环神经网络LSTM第八篇:李沐动手学深度学习V2-LSTM长短期记忆网络以及代码实现
深度循环神经网络第九篇:李沐动手学深度学习V2-深度循环神经网络和代码实现
双向循环神经网络第十篇:李沐动手学深度学习V2-双向循环神经网络Bidirectional RNN和代码实现

李沐动手学深度学习V2-RNN循环神经网络原理相关推荐

  1. 李沐动手学深度学习v2/总结1

    总结 编码过程 数据 数据预处理 模型 参数,初始化参数 超参数 损失函数,先计算损失,清空梯度(防止有累积的梯度),再对损失后向传播计算损失关于参数的梯度 优化算法,使用优化算法更新参数 训练求参数 ...

  2. 14李沐动手学深度学习v2/权重衰退简洁实现

    # 权重衰退是广泛应用的正则化技术 %matplotlib inline import torch from torch import nn from d2l import torch as d2l ...

  3. 李沐动手学深度学习V2-全卷积网络FCN和代码实现

    一.全卷积网络FCN 1. 介绍 语义分割是对图像中的每个像素分类,全卷积网络(fully convolutional network,FCN)采用卷积神经网络实现了从图像像素到像素类别的变换 ,与前 ...

  4. 李沐动手学深度学习(pytorch版本)d2lzh_pytorch包的缺少安装问题

    学习深度学习时候,很多人参考的是李沐的动手学深度学习Pytorch版本(附上官方地址:https://tangshusen.me/Dive-into-DL-PyTorch/#/). 在学习3.5.1节 ...

  5. 【李沐动手学深度学习】读书笔记 01前言

    虽然之前已经学过这部分内容和深度学习中的基础知识,但总觉得学的不够系统扎实,所以希望再通过沐神的课程以及书籍,系统条理的学习一遍.在读书过程中,利用导图做了一下梳理,形成了这个读书笔记.如有侵权,请联 ...

  6. 关于李沐动手学深度学习(d2l)pytorch环境本地配置

    本地安装d2l 由于之前试了很多次d2l课本的安装方法失败了,这里提供一种我可以成功安装d2l包的方法. pytorch安装 首先安装cuda.cudnn.pytroch(gpu版本).可以参考这篇文 ...

  7. 李沐动手学深度学习:08 线性回归(代码逐行理解)

    目录 一.相关资料连接 1.1 李沐视频 1.2 代码.PPT 二.代码及笔记(使用Jupyter Notebook) 2.1 线性回归从零开始实现 2.1.1 基本概念 2.1.2 基础优化算法 2 ...

  8. windows上配置深度学习(李沐-动手学深度学习)

    1.安装miniconda windows下安装,去清华大学开源镜像下载,速度比较快. 选中Miniconda3-latest-Windos-x86_64.exe下载安装包(目前最新的是py3.9) ...

  9. 《动手学深度学习》task3_3 循环神经网络进阶

    目录 GRU GRU 重置门和更新门 候选隐藏状态 隐藏状态 GRU的实现 载入数据集 初始化参数 GRU模型 训练模型 简洁实现 LSTM 长短期记忆 输入门.遗忘门和输出门 候选记忆细胞 记忆细胞 ...

  10. 动手学深度学习PyTorch版-循环神经网络基础

    循环神经网络基础 从零开始实现循环神经网络 import torch import torch.nn as nn import time import math import sys sys.path ...

最新文章

  1. python3 python2 字符串与hex互转区别
  2. Qt使用dmctk时的错误
  3. python笔记之利用scrapy框架爬取糗事百科首页段子
  4. linux安装icc步骤,怎麼安装不到 icc?
  5. 数据备份资深老牌厂商 Commvault 的新玩法
  6. caioj:1682: 【贪心】买一送一
  7. JavaEE基础(十七)/集合
  8. JSJQuery必备技能
  9. Java中==和equals()的区别
  10. 服务器(Windows镜像)自建git服务器超详细教程
  11. android 拼音搜索汉字,android开发之使用拼音搜索汉字
  12. 交换机的工作原理是什么,它有什么功能与作用?
  13. php 色彩空间转换,PHP Imagemagick将灰度转换为RGB
  14. 使用微信号开通检测软件的成功案例(一)
  15. Android项目开发:简易计步器
  16. st_contains
  17. 案例研究:中国金融科技50强之“安心de利”风控模式
  18. Webmail攻防实战
  19. PSPACE完全性学习笔记
  20. ORACLE建用户赋予权限

热门文章

  1. 台式计算机显示不了无线网络,台式电脑无线网卡不显示wifi,电脑怎样连接wifi
  2. 由I2C data信号低电平不到0,再思考I2C及GPIO
  3. Linux_admin-练习
  4. 计算机网络基础学习笔记
  5. 一篇通读网贷产品的身份核验设计
  6. 安装VMware Workstation 14
  7. Java随笔记录第三章:数组
  8. 完全但不完美信息博弈
  9. html5 邮箱后缀自动填写,JS输入用户名自动显示邮箱后缀列表的方法
  10. Python爬虫基础讲解(二十七):scrapy 框架—ltem和scrapy.Request