RNN的基本结构与前向传播

一、RNN的基本结构

1.1 基本单元结构

循环神经网络的基本单元(RNN Cell)由以下部分组成:

  • 输入门:接收当前时间步的输入数据
  • 隐藏状态:存储之前的信息,作为网络的"记忆"
  • 输出门:产生当前时间步的输出
  • 循环连接:将隐藏状态的输出反馈到输入,形成循环
          +------------------------+
          |                        |
          v                        |
+---------+---------+    +---------+---------+
|                   |    |                   |
|  输入 x(t)        |--->|  隐藏状态 h(t)    |---> 输出 y(t)
|                   |    |                   |
+-------------------+    +-------------------+

1.2 展开的RNN结构

为了更好地理解RNN的工作过程,我们可以将其按时间步展开:

t=1                t=2                t=3
+---------+       +---------+       +---------+
|         |       |         |       |         |
|  x(1)   |-----> |  x(2)   |-----> |  x(3)   |
|         |       |         |       |         |
+---------+       +---------+       +---------+
    |                 |                 |
    v                 v                 v
+---------+       +---------+       +---------+
|         |       |         |       |         |
|  h(1)   |<----->|  h(2)   |<----->|  h(3)   |
|         |       |         |       |         |
+---------+       +---------+       +---------+
    |                 |                 |
    v                 v                 v
+---------+       +---------+       +---------+
|         |       |         |       |         |
|  y(1)   |       |  y(2)   |       |  y(3)   |
|         |       |         |       |         |
+---------+       +---------+       +---------+

在展开的结构中,我们可以看到:

  1. 每个时间步都有一个输入 x(t)
  2. 每个时间步都有一个隐藏状态 h(t)
  3. 每个时间步都有一个输出 y(t)
  4. 隐藏状态 h(t) 不仅取决于当前输入 x(t) ,还取决于上一个时间步的隐藏状态 h(t-1)

1.3 RNN的参数

RNN包含以下参数:

  • **输入权重 W_{xh} **:连接输入 x(t) 到隐藏状态 h(t)
  • **循环权重 W_{hh} **:连接上一个隐藏状态 h(t-1) 到当前隐藏状态 h(t)
  • **隐藏偏置 b_h **:隐藏状态的偏置项
  • **输出权重 W_{hy} **:连接隐藏状态 h(t) 到输出 y(t)
  • **输出偏置 b_y **:输出的偏置项

这些参数在所有时间步中共享,这是RNN的一个重要特点。

二、RNN的数学原理

2.1 隐藏状态的计算

RNN的核心是隐藏状态的计算,它综合了当前输入和之前的隐藏状态:

h(t) = anh(W_{xh} dot x(t) + W_{hh} dot h(t-1) + b_h)

其中:

  • anh  是激活函数,用于引入非线性
  • x(t) 是当前时间步的输入
  • h(t-1) 是上一个时间步的隐藏状态
  • W_{xh}, W_{hh}, b_h 是模型参数

2.2 输出的计算

当前时间步的输出由隐藏状态计算得到:

y(t) = W_{hy} dot h(t) + b_y

对于分类任务,通常会在输出层后添加softmax激活函数:

at{y}(t) = ext{softmax}(y(t))

2.3 初始隐藏状态

在处理序列的第一个元素时,需要一个初始隐藏状态 h(0) 。通常,我们将其初始化为全零向量:

h(0) = 0

三、RNN的前向传播过程

3.1 前向传播步骤

RNN的前向传播过程按时间步依次进行:

  1. 初始化:设置初始隐藏状态 h(0) = 0
  2. 时间步 t=1
    • 输入 x(1)
    • 计算隐藏状态 h(1) = anh(W_{xh} dot x(1) + W_{hh} dot h(0) + b_h)
    • 计算输出 y(1) = W_{hy} dot h(1) + b_y
  3. 时间步 t=2
    • 输入 x(2)
    • 计算隐藏状态 h(2) = anh(W_{xh} dot x(2) + W_{hh} dot h(1) + b_h)
    • 计算输出 y(2) = W_{hy} dot h(2) + b_y
  4. 重复:直到处理完整个序列的所有时间步
  5. 返回:所有时间步的输出和最终的隐藏状态

3.2 前向传播示例

假设我们有一个简单的RNN,参数如下:

  • W_{xh} = [[0.1, 0.2], [0.3, 0.4]]
  • W_{hh} = [[0.5, 0.6], [0.7, 0.8]]
  • b_h = [0.1, 0.2]
  • W_{hy} = [[0.9, 0.8], [0.7, 0.6]]
  • b_y = [0.1, 0.2]

输入序列为:

  • x(1) = [1.0, 2.0]
  • x(2) = [3.0, 4.0]

计算过程

  1. 初始化: h(0) = [0, 0]

  2. 时间步 t=1

    • W_{xh} dot x(1) = [[0.1, 0.2], [0.3, 0.4]] dot [1.0, 2.0] = [0.5, 1.1]
    • W_{hh} dot h(0) = [[0.5, 0.6], [0.7, 0.8]] dot [0, 0] = [0, 0]
    • h(1) = anh([0.5, 1.1] + [0, 0] + [0.1, 0.2]) = anh([0.6, 1.3]) ≈ [0.537, 0.861]
    • y(1) = [[0.9, 0.8], [0.7, 0.6]] dot [0.537, 0.861] + [0.1, 0.2] ≈ [1.33, 1.09]
  3. 时间步 t=2

    • W_{xh} dot x(2) = [[0.1, 0.2], [0.3, 0.4]] dot [3.0, 4.0] = [1.1, 2.5]
    • W_{hh} dot h(1) = [[0.5, 0.6], [0.7, 0.8]] dot [0.537, 0.861] ≈ [0.775, 1.056]
    • h(2) = anh([1.1, 2.5] + [0.775, 1.056] + [0.1, 0.2]) = anh([1.975, 3.756]) ≈ [0.964, 0.999]
    • y(2) = [[0.9, 0.8], [0.7, 0.6]] dot [0.964, 0.999] + [0.1, 0.2] ≈ [1.87, 1.47]

3.3 向量化计算

为了提高计算效率,实际实现中通常使用向量化计算,同时处理整个批次的序列:

  • 输入:形状为 (batch_size, seq_length, input_size)
  • 隐藏状态:形状为 (num_layers, batch_size, hidden_size)
  • 输出:形状为 (batch_size, seq_length, output_size)

向量化计算大大提高了RNN的训练和推理速度。

四、不同类型的RNN结构

4.1 一对一结构

输入是单个向量,输出也是单个向量,类似于传统的前馈神经网络。这种结构通常用于不需要考虑序列关系的任务。

+---------+       +---------+       +---------+
|         |       |         |       |         |
| 输入 x  | ----> | 隐藏层h | ----> | 输出 y  |
|         |       |         |       |         |
+---------+       +---------+       +---------+

4.2 一对多结构

输入是单个向量,输出是一个序列。这种结构常用于生成任务,如图像描述生成。

                  时间步 t=1        时间步 t=2        时间步 t=3
+---------+       +---------+       +---------+       +---------+
|         |       |         |       |         |       |         |
| 输入 x  | ----> | 隐藏层h1| ----> | 隐藏层h2| ----> | 隐藏层h3|
|         |       |         |       |         |       |         |
+---------+       +---------+       +---------+       +---------+
                        |                 |                 |
                        v                 v                 v
                  +---------+       +---------+       +---------+
                  |         |       |         |       |         |
                  | 输出 y1 |       | 输出 y2 |       | 输出 y3 |
                  |         |       |         |       |         |
                  +---------+       +---------+       +---------+

4.3 多对一结构

输入是一个序列,输出是单个向量。这种结构常用于序列分类任务,如情感分析。

时间步 t=1        时间步 t=2        时间步 t=3        +---------+
+---------+       +---------+       +---------+       |         |
|         |       |         |       |         |       | 输出 y  |
| 输入 x1 | ----> | 输入 x2 | ----> | 输入 x3 | ----> |         |
|         |       |         |       |         |       +---------+
+---------+       +---------+       +---------+
    |                 |                 |
    v                 v                 v
+---------+       +---------+       +---------+
|         |       |         |       |         |
| 隐藏层h1|<----->| 隐藏层h2|<----->| 隐藏层h3|
|         |       |         |       |         |
+---------+       +---------+       +---------+

4.4 多对多结构(同步)

输入是一个序列,输出也是一个序列,且输入和输出的长度相同。这种结构常用于序列标注任务,如命名实体识别。

时间步 t=1        时间步 t=2        时间步 t=3
+---------+       +---------+       +---------+
|         |       |         |       |         |
| 输入 x1 | ----> | 输入 x2 | ----> | 输入 x3 |
|         |       |         |       |         |
+---------+       +---------+       +---------+
    |                 |                 |
    v                 v                 v
+---------+       +---------+       +---------+
|         |       |         |       |         |
| 隐藏层h1|<----->| 隐藏层h2|<----->| 隐藏层h3|
|         |       |         |       |         |
+---------+       +---------+       +---------+
    |                 |                 |
    v                 v                 v
+---------+       +---------+       +---------+
|         |       |         |       |         |
| 输出 y1 |       | 输出 y2 |       | 输出 y3 |
|         |       |         |       |         |
+---------+       +---------+       +---------+

4.5 多对多结构(异步)

输入是一个序列,输出也是一个序列,但输入和输出的长度可以不同。这种结构常用于机器翻译等任务,通常采用编码器-解码器架构。

编码器部分(处理输入序列):
时间步 t=1        时间步 t=2        时间步 t=3
+---------+       +---------+       +---------+
|         |       |         |       |         |
| 输入 x1 | ----> | 输入 x2 | ----> | 输入 x3 |
|         |       |         |       |         |
+---------+       +---------+       +---------+
    |                 |                 |
    v                 v                 v
+---------+       +---------+       +---------+
|         |       |         |       |         |
| 隐藏层h1|<----->| 隐藏层h2|<----->| 隐藏层h3|
|         |       |         |       |         |
+---------+       +---------+       +---------+
                                        |
                                        v
解码器部分(生成输出序列):
                  时间步 t=1        时间步 t=2        时间步 t=3
                  +---------+       +---------+       +---------+
                  |         |       |         |       |         |
                  | 输入 y0 | ----> | 输入 y1 | ----> | 输入 y2 |
                  |         |       |         |       |         |
                  +---------+       +---------+       +---------+
                        |                 |                 |
                        v                 v                 v
                  +---------+       +---------+       +---------+
                  |         |       |         |       |         |
                  | 隐藏层d1|<----->| 隐藏层d2|<----->| 隐藏层d3|
                  |         |       |         |       |         |
                  +---------+       +---------+       +---------+
                        |                 |                 |
                        v                 v                 v
                  +---------+       +---------+       +---------+
                  |         |       |         |       |         |
                  | 输出 y1 |       | 输出 y2 |       | 输出 y3 |
                  |         |       |         |       |         |
                  +---------+       +---------+       +---------+

五、RNN的参数计算

5.1 参数数量计算

RNN的参数数量取决于输入维度、隐藏状态维度和输出维度:

  • 输入到隐藏的权重: W_{xh} 的形状为 (hidden_size, input_size)
  • 隐藏到隐藏的权重: W_{hh} 的形状为 (hidden_size, hidden_size)
  • 隐藏层偏置: b_h 的形状为 (hidden_size, 1)
  • 隐藏到输出的权重: W_{hy} 的形状为 (output_size, hidden_size)
  • 输出层偏置: b_y 的形状为 (output_size, 1)

总参数数量为:

 ext{参数数量} = hiddensize 	imes (inputsize + hiddensize + 1) + outputsize 	imes (hiddensize + 1) 

5.2 参数共享

RNN的一个重要特点是参数共享:

  • 时间步共享:所有时间步使用相同的参数集
  • 参数效率:减少了模型的参数量,提高了泛化能力
  • 模式学习:有助于学习序列中的通用模式

六、RNN的代码实现

6.1 使用Python实现基本的RNN前向传播

import numpy as np

def rnn_forward(x, h_prev, Wxh, Whh, Why, bh, by):
    """
    RNN前向传播
    x: 输入序列,形状为 (seq_length, input_size)
    h_prev: 初始隐藏状态,形状为 (hidden_size,)
    Wxh: 输入到隐藏的权重,形状为 (hidden_size, input_size)
    Whh: 隐藏到隐藏的权重,形状为 (hidden_size, hidden_size)
    Why: 隐藏到输出的权重,形状为 (output_size, hidden_size)
    bh: 隐藏层偏置,形状为 (hidden_size,)
    by: 输出层偏置,形状为 (output_size,)
    """
    seq_length, input_size = x.shape
    hidden_size = Wxh.shape[0]
    output_size = Why.shape[0]
    
    # 存储隐藏状态和输出
    hs = np.zeros((seq_length, hidden_size))
    ys = np.zeros((seq_length, output_size))
    
    h = h_prev
    
    for t in range(seq_length):
        # 获取当前时间步的输入
        x_t = x[t]
        
        # 计算隐藏状态
        h = np.tanh(np.dot(Wxh, x_t) + np.dot(Whh, h) + bh)
        
        # 计算输出
        y = np.dot(Why, h) + by
        
        # 存储结果
        hs[t] = h
        ys[t] = y
    
    return ys, hs, h

# 示例使用
if __name__ == "__main__":
    # 定义参数
    seq_length = 3
    input_size = 2
    hidden_size = 2
    output_size = 2
    
    # 初始化参数
    Wxh = np.random.randn(hidden_size, input_size) * 0.01
    Whh = np.random.randn(hidden_size, hidden_size) * 0.01
    Why = np.random.randn(output_size, hidden_size) * 0.01
    bh = np.zeros(hidden_size)
    by = np.zeros(output_size)
    
    # 生成输入序列
    x = np.random.randn(seq_length, input_size)
    
    # 初始化隐藏状态
    h_prev = np.zeros(hidden_size)
    
    # 前向传播
    ys, hs, h_final = rnn_forward(x, h_prev, Wxh, Whh, Why, bh, by)
    
    print("输入序列:")
    print(x)
    print("\n隐藏状态序列:")
    print(hs)
    print("\n输出序列:")
    print(ys)
    print("\n最终隐藏状态:")
    print(h_final)

6.2 使用PyTorch实现RNN

import torch
import torch.nn as nn

class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        
        self.hidden_size = hidden_size
        
        # 定义RNN的线性层
        self.Wxh = nn.Linear(input_size, hidden_size)
        self.Whh = nn.Linear(hidden_size, hidden_size)
        self.Why = nn.Linear(hidden_size, output_size)
        
    def forward(self, x, h_prev=None):
        """
        前向传播
        x: 输入序列,形状为 (batch_size, seq_length, input_size)
        h_prev: 初始隐藏状态,形状为 (batch_size, hidden_size)
        """
        batch_size, seq_length, input_size = x.size()
        
        # 初始化隐藏状态
        if h_prev is None:
            h_prev = torch.zeros(batch_size, self.hidden_size).to(x.device)
        
        # 存储隐藏状态和输出
        hs = []
        ys = []
        
        h = h_prev
        
        for t in range(seq_length):
            # 获取当前时间步的输入
            x_t = x[:, t, :]
            
            # 计算隐藏状态
            h = torch.tanh(self.Wxh(x_t) + self.Whh(h))
            
            # 计算输出
            y = self.Why(h)
            
            # 存储结果
            hs.append(h.unsqueeze(1))
            ys.append(y.unsqueeze(1))
        
        # 拼接结果
        hs = torch.cat(hs, dim=1)
        ys = torch.cat(ys, dim=1)
        
        return ys, hs, h

# 示例使用
if __name__ == "__main__":
    # 定义参数
    batch_size = 2
    seq_length = 3
    input_size = 2
    hidden_size = 2
    output_size = 2
    
    # 创建模型
    model = SimpleRNN(input_size, hidden_size, output_size)
    
    # 生成输入
    x = torch.randn(batch_size, seq_length, input_size)
    
    # 前向传播
    ys, hs, h_final = model(x)
    
    print("输入形状:", x.shape)
    print("输出形状:", ys.shape)
    print("隐藏状态形状:", hs.shape)
    print("最终隐藏状态形状:", h_final.shape)
    
    # 使用PyTorch内置的RNN
    print("\n使用PyTorch内置的RNN:")
    rnn = nn.RNN(input_size, hidden_size, batch_first=True)
    output, hn = rnn(x)
    print("内置RNN输出形状:", output.shape)
    print("内置RNN最终隐藏状态形状:", hn.shape)

6.3 使用TensorFlow实现RNN

import tensorflow as tf

class SimpleRNN(tf.keras.Model):
    def __init__(self, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        self.hidden_size = hidden_size
        self.rnn_cell = tf.keras.layers.SimpleRNNCell(hidden_size)
        self.dense = tf.keras.layers.Dense(output_size)
    
    def call(self, inputs, initial_state=None):
        # 输入形状: (batch_size, seq_length, input_size)
        batch_size, seq_length, input_size = inputs.shape
        
        if initial_state is None:
            initial_state = tf.zeros((batch_size, self.hidden_size))
        
        # 存储隐藏状态和输出
        hidden_states = []
        outputs = []
        
        state = initial_state
        
        for t in range(seq_length):
            # 获取当前时间步的输入
            x_t = inputs[:, t, :]
            
            # 计算隐藏状态
            output, state = self.rnn_cell(x_t, state)
            
            # 计算最终输出
            final_output = self.dense(output)
            
            # 存储结果
            hidden_states.append(state)
            outputs.append(final_output)
        
        # 拼接结果
        hidden_states = tf.stack(hidden_states, axis=1)
        outputs = tf.stack(outputs, axis=1)
        
        return outputs, hidden_states, state

# 示例使用
if __name__ == "__main__":
    # 定义参数
    batch_size = 2
    seq_length = 3
    input_size = 2
    hidden_size = 2
    output_size = 2
    
    # 创建模型
    model = SimpleRNN(hidden_size, output_size)
    
    # 生成输入
    inputs = tf.random.normal((batch_size, seq_length, input_size))
    
    # 前向传播
    outputs, hidden_states, final_state = model(inputs)
    
    print("输入形状:", inputs.shape)
    print("输出形状:", outputs.shape)
    print("隐藏状态形状:", hidden_states.shape)
    print("最终隐藏状态形状:", final_state.shape)
    
    # 使用TensorFlow内置的RNN
    print("\n使用TensorFlow内置的RNN:")
    rnn_layer = tf.keras.layers.SimpleRNN(hidden_size, return_sequences=True, return_state=True)
    output, state = rnn_layer(inputs)
    print("内置RNN输出形状:", output.shape)
    print("内置RNN最终隐藏状态形状:", state.shape)

七、RNN的实际应用示例

7.1 字符级语言模型

字符级语言模型使用RNN预测下一个字符,给定前几个字符:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

# 准备数据
text = "hello world"
chars = list(set(text))
char_to_idx = {char: idx for idx, char in enumerate(chars)}
idx_to_char = {idx: char for idx, char in enumerate(chars)}

# 超参数
seq_length = 3
hidden_size = 10
learning_rate = 0.01
epochs = 1000

# 准备训练数据
data = [char_to_idx[char] for char in text]
x_data = []
y_data = []

for i in range(len(data) - seq_length):
    x_seq = data[i:i+seq_length]
    y_seq = data[i+1:i+seq_length+1]
    x_data.append(x_seq)
    y_data.append(y_seq)

x_data = torch.tensor(x_data, dtype=torch.long)
y_data = torch.tensor(y_data, dtype=torch.long)

# 转换为one-hot编码
def one_hot_encode(x, num_classes):
    batch_size, seq_length = x.shape
    one_hot = torch.zeros(batch_size, seq_length, num_classes)
    for i in range(batch_size):
        for j in range(seq_length):
            one_hot[i, j, x[i, j]] = 1
    return one_hot

x_one_hot = one_hot_encode(x_data, len(chars))

# 定义模型
class CharRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(CharRNN, self).__init__()
        self.hidden_size = hidden_size
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
    
    def forward(self, x, h_prev=None):
        out, h = self.rnn(x, h_prev)
        out = self.fc(out)
        return out, h

# 创建模型
model = CharRNN(len(chars), hidden_size, len(chars))
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 训练模型
for epoch in range(epochs):
    optimizer.zero_grad()
    output, _ = model(x_one_hot)
    loss = criterion(output.view(-1, len(chars)), y_data.view(-1))
    loss.backward()
    optimizer.step()
    
    if (epoch + 1) % 100 == 0:
        print(f"Epoch: {epoch+1}, Loss: {loss.item():.4f}")

# 生成文本
def generate_text(model, start_seq, length=10):
    model.eval()
    with torch.no_grad():
        # 初始化输入
        input_seq = torch.tensor([[char_to_idx[char] for char in start_seq]], dtype=torch.long)
        input_one_hot = one_hot_encode(input_seq, len(chars))
        
        # 初始化隐藏状态
        h = None
        
        # 生成文本
        generated = start_seq
        
        for _ in range(length):
            output, h = model(input_one_hot, h)
            # 预测下一个字符
            prob = torch.softmax(output[:, -1, :], dim=1)
            next_char_idx = torch.multinomial(prob, 1).item()
            next_char = idx_to_char[next_char_idx]
            
            # 添加到生成的文本中
            generated += next_char
            
            # 更新输入
            input_seq = torch.tensor([[next_char_idx]], dtype=torch.long)
            input_one_hot = one_hot_encode(input_seq, len(chars))
        
        return generated

# 测试生成文本
print("\n生成文本:")
print(generate_text(model, "hel", length=10))

7.2 简单的情感分析

使用RNN进行简单的情感分析,判断句子的情感倾向:

import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim

# 准备数据
sentences = ["I love this movie", "This film is great", "I hate this film", "This movie is terrible"]
sentiments = [1, 1, 0, 0]  # 1: positive, 0: negative

# 构建词汇表
words = set()
for sentence in sentences:
    words.update(sentence.lower().split())
word_to_idx = {word: idx for idx, word in enumerate(words)}

# 超参数
embedding_dim = 5
hidden_size = 10
learning_rate = 0.01
epochs = 1000

# 准备训练数据
x_data = []
y_data = []

for sentence, sentiment in zip(sentences, sentiments):
    word_indices = [word_to_idx[word.lower()] for word in sentence.split()]
    x_data.append(word_indices)
    y_data.append(sentiment)

# 填充序列到相同长度
max_length = max(len(seq) for seq in x_data)
x_padded = []
for seq in x_data:
    padded = seq + [0] * (max_length - len(seq))
    x_padded.append(padded)

x_data = torch.tensor(x_padded, dtype=torch.long)
y_data = torch.tensor(y_data, dtype=torch.float32)

# 定义模型
class SentimentRNN(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_size, output_size):
        super(SentimentRNN, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.rnn = nn.RNN(embedding_dim, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)
        self.sigmoid = nn.Sigmoid()
    
    def forward(self, x):
        # 嵌入层
        embedded = self.embedding(x)
        
        # RNN层
        out, h = self.rnn(embedded)
        
        # 使用最后一个时间步的隐藏状态
        out = self.fc(h.squeeze(0))
        out = self.sigmoid(out)
        
        return out

# 创建模型
model = SentimentRNN(len(words), embedding_dim, hidden_size, 1)
criterion = nn.BCELoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# 训练模型
for epoch in range(epochs):
    optimizer.zero_grad()
    output = model(x_data)
    loss = criterion(output.squeeze(), y_data)
    loss.backward()
    optimizer.step()
    
    if (epoch + 1) % 100 == 0:
        # 计算准确率
        predictions = (output.squeeze() > 0.5).float()
        accuracy = (predictions == y_data).float().mean()
        print(f"Epoch: {epoch+1}, Loss: {loss.item():.4f}, Accuracy: {accuracy.item():.4f}")

# 测试模型
test_sentences = ["I love this film", "This movie is terrible"]
print("\n测试结果:")
for sentence in test_sentences:
    word_indices = [word_to_idx[word.lower()] for word in sentence.split()]
    padded = word_indices + [0] * (max_length - len(word_indices))
    input_tensor = torch.tensor([padded], dtype=torch.long)
    output = model(input_tensor)
    sentiment = "positive" if output.item() > 0.5 else "negative"
    print(f"Sentence: {sentence}, Sentiment: {sentiment}")

八、总结与思考

通过本教程的学习,我们详细了解了RNN的基本结构和前向传播过程:

  1. 基本结构:RNN由输入门、隐藏状态、输出门和循环连接组成,通过隐藏状态存储之前的信息
  2. 数学原理:隐藏状态的计算综合了当前输入和之前的隐藏状态,输出由隐藏状态计算得到
  3. 前向传播:按时间步依次计算,将隐藏状态的输出反馈到输入,形成循环
  4. 不同结构:根据输入和输出的形式,RNN可以分为一对一、一对多、多对一和多对多等结构
  5. 代码实现:我们使用Python、PyTorch和TensorFlow实现了RNN的前向传播
  6. 实际应用:通过字符级语言模型和情感分析的示例,展示了RNN的应用

思考问题

  1. RNN中的循环连接有什么作用?它如何帮助RNN处理序列数据?
  2. 为什么RNN在所有时间步共享参数?这有什么优势和劣势?
  3. 在RNN的前向传播中,隐藏状态的计算为什么使用tanh激活函数?可以使用其他激活函数吗?
  4. 对于不同长度的序列,RNN是如何处理的?
  5. 你认为RNN最适合处理哪些类型的任务?为什么?
« 上一篇 循环神经网络(RNN)的提出与动机 下一篇 » RNN处理序列数据的优势与原理