EliorFoy 发表于 2024-11-10 20:34

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

[个人博客]

# (一) 文本预处理
对一段文字进行统计预测,首先得进行处理,将字符串处理为单词、字符等词元.
步骤如下:
## 1. 读取数据集
简单地将文本的每一行读入,可以使用H.G.Well的"time_machine"数据集:
```python
#@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 +', ' ', line).strip().lower() for line in lines]

lines = read_time_machine()
```
## 2. 词元化
将每一行又分解为若干词语:
```python
def tokenize(lines, token='word'):#@save
    """将文本行拆分为单词或字符词元"""
    if token == 'word':
      return
    elif token == 'char':
      return
    else:
      print('错误:未知词元类型:' + token)

tokens = tokenize(lines)
```
## 3.构建字典词表
将字符映射为字典的数字索引,统计词语出现的频率来分配索引,很少出现的词语会被移除并被映射到一个未知词元“\<unk>”,另外还有一些特殊词元例如:填充词元(“\<pad>”); 序列开始词元(“\<bos>”); 序列结束词元(“\<eos>”).
```python
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,
                                 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 = 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

    def to_tokens(self, indices):
      if not isinstance(indices, (list, tuple)):
            return self.idx_to_token
      return 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, list):
      # 将词元列表展平成一个列表
      tokens =
    return collections.Counter(tokens)
```
# (二) 语言模型与循环神经网络
语言模型通俗的来讲是:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么.对于一段文字来讲,如果模型能够理解一整段话,那就不能只把一段话拆分为几个词语单独分析,需要结合这个词语的前文以及后文(加上后文就成了双向RNN)来综合分析.
一个基本的循环神经网络如下:
![循环神经网络图](https://picx.zhimg.com/70/v2-8e0301109ac2a87ef69d79f17d6c852c_1440w.avis?source=172ae18b&biz_tag=Post)
$$
\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}
$$
# (三) 循环神经网络的实现
这里就直接看使用框架的简洁实现:
```python
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以张量作为隐状态
            returntorch.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](https://img.picui.cn/free/2024/11/09/672e4adb5f445.png)
$$
\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](https://img.picui.cn/free/2024/11/09/672e4af926128.png)

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

秦天qintian0303 发表于 2024-11-11 23:41

<p>隐藏层一般设计多少层比较好,还是自己能分析?</p>

ljg2np 发表于 2024-11-21 15:29

<p>隐藏层的层数与单层神经元个数,作为神经网络设计的可变因素,与训练数据的信息量关系密切。</p>
页: [1]
查看完整版本: 动手学深度学习(七):循环神经网络与现代循环神经网络