动手学深度学习(七):循环神经网络与现代循环神经网络

EliorFoy Lv3

(一) 文本预处理

对一段文字进行统计预测,首先得进行处理,将字符串处理为单词、字符等词元.
步骤如下:

1. 读取数据集

简单地将文本的每一行读入,可以使用H.G.Well的"time_machine"数据集:

1
2
3
4
5
6
7
8
9
10
11
#@save
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt',
'090b5e7e70c295757f55df93cb0a180b9691891a')

def read_time_machine(): #@save
"""将时间机器数据集加载到文本行的列表中"""
with open(d2l.download('time_machine'), 'r') as f:
lines = f.readlines()
return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]

lines = read_time_machine()

2. 词元化

将每一行又分解为若干词语:

1
2
3
4
5
6
7
8
9
10
def tokenize(lines, token='word'):  #@save
"""将文本行拆分为单词或字符词元"""
if token == 'word':
return [line.split() for line in lines]
elif token == 'char':
return [list(line) for line in lines]
else:
print('错误:未知词元类型:' + token)

tokens = tokenize(lines)

3.构建字典词表

将字符映射为字典的数字索引,统计词语出现的频率来分配索引,很少出现的词语会被移除并被映射到一个未知词元“<unk>”,另外还有一些特殊词元例如:填充词元(“<pad>”); 序列开始词元(“<bos>”); 序列结束词元(“<eos>”).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class Vocab:  #@save
"""文本词表"""
def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
if tokens is None:
tokens = []
if reserved_tokens is None:
reserved_tokens = []
# 按出现频率排序
counter = count_corpus(tokens)
self._token_freqs = sorted(counter.items(), key=lambda x: x[1],
reverse=True)
# 未知词元的索引为0
self.idx_to_token = ['<unk>'] + reserved_tokens
self.token_to_idx = {token: idx
for idx, token in enumerate(self.idx_to_token)}
for token, freq in self._token_freqs:
if freq < min_freq:
break
if token not in self.token_to_idx:
self.idx_to_token.append(token)
self.token_to_idx[token] = len(self.idx_to_token) - 1

def __len__(self):
return len(self.idx_to_token)

def __getitem__(self, tokens):
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]

def to_tokens(self, indices):
if not isinstance(indices, (list, tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]

@property
def unk(self): # 未知词元的索引为0
return 0

@property
def token_freqs(self):
return self._token_freqs

def count_corpus(tokens): #@save
"""统计词元的频率"""
# 这里的tokens是1D列表或2D列表
if len(tokens) == 0 or isinstance(tokens[0], list):
# 将词元列表展平成一个列表
tokens = [token for line in tokens for token in line]
return collections.Counter(tokens)

(二) 语言模型与循环神经网络

语言模型通俗的来讲是:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么.对于一段文字来讲,如果模型能够理解一整段话,那就不能只把一段话拆分为几个词语单独分析,需要结合这个词语的前文以及后文(加上后文就成了双向RNN)来综合分析.
一个基本的循环神经网络如下:
循环神经网络图

0t=g(Vst)=Vf(Uxt+Wst1)=Vf(Uxt+Wf(Uxt1+Wst2))=Vf(Uxt+Wf(Uxt1+Wf(Uxt2+Wst3)))=Vf(Uxt+Wf(Uxt1+Wf(Uxt2+Wf(Uxt3+))))\begin{aligned} 0_{t}& =g(V\mathrm{s}_{t}) \\ &=Vf(U\mathbf{x}_t+W\mathbf{s}_{t-1}) \\ &=Vf(U\mathbf{x}_{t}+Wf(U\mathbf{x}_{t-1}+W\mathbf{s}_{t-2})) \\ &=Vf(U\mathbf{x}_t+Wf(U\mathbf{x}_{t-1}+Wf(U\mathbf{x}_{t-2}+W\mathbf{s}_{t-3}))) \\ &=Vf(U\mathbf{x}_{t}+Wf(U\mathbf{x}_{t-1}+Wf(U\mathbf{x}_{t-2}+Wf(U\mathbf{x}_{t-3}+\ldots)))) \end{aligned}

(三) 循环神经网络的实现

这里就直接看使用框架的简洁实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
num_hiddens = 256
rnn_layer = nn.RNN(len(vocab), num_hiddens)
state = torch.zeros((1, batch_size, num_hiddens)) # 初始化隐状态
X = torch.rand(size=(num_steps, batch_size, len(vocab)))
Y, state_new = rnn_layer(X, state)
#@save
class RNNModel(nn.Module):
"""循环神经网络模型"""
def __init__(self, rnn_layer, vocab_size, **kwargs):
super(RNNModel, self).__init__(**kwargs)
self.rnn = rnn_layer
self.vocab_size = vocab_size
self.num_hiddens = self.rnn.hidden_size
# 如果RNN是双向的(之后将介绍),num_directions应该是2,否则应该是1
if not self.rnn.bidirectional:
self.num_directions = 1
self.linear = nn.Linear(self.num_hiddens, self.vocab_size)
else:
self.num_directions = 2
self.linear = nn.Linear(self.num_hiddens * 2, self.vocab_size)

def forward(self, inputs, state):
X = F.one_hot(inputs.T.long(), self.vocab_size)
X = X.to(torch.float32)
Y, state = self.rnn(X, state)
# 全连接层首先将Y的形状改为(时间步数*批量大小,隐藏单元数)
# 它的输出形状是(时间步数*批量大小,词表大小)。
output = self.linear(Y.reshape((-1, Y.shape[-1])))
return output, state

def begin_state(self, device, batch_size=1):
if not isinstance(self.rnn, nn.LSTM):
# nn.GRU以张量作为隐状态
return torch.zeros((self.num_directions * self.rnn.num_layers,
batch_size, self.num_hiddens),
device=device)
else:
# nn.LSTM以元组作为隐状态
return (torch.zeros((
self.num_directions * self.rnn.num_layers,
batch_size, self.num_hiddens), device=device),
torch.zeros((
self.num_directions * self.rnn.num_layers,
batch_size, self.num_hiddens), device=device))
# 训练
device = d2l.try_gpu()
net = RNNModel(rnn_layer, vocab_size=len(vocab))
net = net.to(device)
d2l.train_ch8('time traveller', 10, net, vocab, device)

(四) 现代循环神经网络

1.双向RNN

双向RNN在之前提到过,增加了反向的隐状态,即:
双向RNN

ot=g(Vst+Vst)st=f(Uxt+Wst1)st=f(Uxt+Wst+1)\begin{aligned} &o_{t} =g(V\mathrm{s}_t+V^{\prime}\mathrm{s}_t^{\prime}) \\ &\mathbf{s}_{t} =f(U\mathbf{x}_t+W\mathbf{s}_{t-1}) \\ &s_{t}^{\prime} =f(U^{\prime}\mathbf{x}_{t}+W^{\prime}\mathbf{s}_{t+1}^{\prime}) \end{aligned}

但是也不能盲目将双向循环神经网络应用于任何预测,有时存在严重缺陷.

2.深度RNN

之前介绍的循环神经网络只有一个隐藏层,堆叠两个以上的隐藏层的时候就得到了深度循环神经网络,如图所示:
深度RNN

老实来讲这一章没有深入读懂,涉及到的随机过程的有些知识例如马尔可夫模型/齐普夫定律等等并没有基础,只能做一个大概的阅读分享.

  • 标题: 动手学深度学习(七):循环神经网络与现代循环神经网络
  • 作者: EliorFoy
  • 创建于 : 2024-11-09 00:48:58
  • 更新于 : 2024-11-09 01:33:18
  • 链接: https://eliorfoy.github.io/2024/11/09/大三上/动手学深度学习(七):循环神经网络与现代循环神经网络/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。