循环神经网络(RNN)入门指南:从原理到实践

守听  金牌会员 | 2024-12-27 22:28:09 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 812|帖子 812|积分 2436

目录
1. 循环神经网络的基本概念
2. 简单循环网络及其应用
3. 参数学习与优化
4. 基于门控的循环神经网络
4.1 长短期影象网络(LSTM)
4.1.1 LSTM的核心组件:
4.2 门控循环单位(GRU)
5 实际应用中的优化技巧
5.1 变体和改进
5.2 注意力机制的结合
6 实现细节和最佳实践
6.1 初始化战略
6.1.1 梯度处理

1. 循环神经网络的基本概念

循环神经网络(Recurrent Neural Network,RNN)是一类具有短期影象能 力的神经网络。在循环神经网络中,神经元不光可以接受其他神经元的信息,也 可以接受自身的信息,形成具有环路的网络布局。
循环神经网络是一类专门用于处理序列数据的神经网络。与传统的前馈神经网络不同,RNN引入了循环毗连,使网络具备了处理时序信息的本领。在处理每个时间步的输入时,网络不仅考虑当前输入,还会利用之前的汗青信息。
      
       循环神经网络       从布局上看,RNN的核心是一个循环单位,它在每个时间步吸收两个输入:当前时候的输入数据和前一时候的隐藏状态。这两个输入经过加权组合和非线性变换,天生当前时候的新隐藏状态。具体来说,在每个时间步t,网络会执行以下盘算:h_t = tanh(W_xh * x_t + W_hh * h_{t-1} + b_h),此中激活函数通常选择tanh或ReLU。
我们通过一个完整的Python实现来深入理解简单循环网络的工作机制:
  1. import numpy as np
  2. class SimpleRNN:
  3.     def __init__(self, input_size, hidden_size, output_size):
  4.         # 初始化网络参数
  5.         self.hidden_size = hidden_size
  6.         self.W_xh = np.random.randn(input_size, hidden_size) * 0.01
  7.         self.W_hh = np.random.randn(hidden_size, hidden_size) * 0.01
  8.         self.W_hy = np.random.randn(hidden_size, output_size) * 0.01
  9.         self.b_h = np.zeros((1, hidden_size))
  10.         self.b_y = np.zeros((1, output_size))
  11.         
  12.         # 用于存储反向传播所需的中间值
  13.         self.hidden_states = []
  14.         self.inputs = []
  15.         
  16.     def forward(self, input_sequence):
  17.         # 初始化隐藏状态
  18.         h = np.zeros((1, self.hidden_size))
  19.         self.hidden_states = [h]
  20.         self.inputs = input_sequence
  21.         outputs = []
  22.         
  23.         # 前向传播
  24.         for x in input_sequence:
  25.             h = np.tanh(np.dot(x, self.W_xh) +
  26.                        np.dot(h, self.W_hh) +
  27.                        self.b_h)
  28.             y = np.dot(h, self.W_hy) + self.b_y
  29.             self.hidden_states.append(h)
  30.             outputs.append(y)
  31.             
  32.         return outputs
  33.    
  34.     def backward(self, d_outputs, learning_rate=0.01):
  35.         # 初始化梯度
  36.         dW_xh = np.zeros_like(self.W_xh)
  37.         dW_hh = np.zeros_like(self.W_hh)
  38.         dW_hy = np.zeros_like(self.W_hy)
  39.         db_h = np.zeros_like(self.b_h)
  40.         db_y = np.zeros_like(self.b_y)
  41.         
  42.         # 反向传播
  43.         dh_next = np.zeros((1, self.hidden_size))
  44.         
  45.         for t in reversed(range(len(self.inputs))):
  46.             # 输出层的梯度
  47.             dy = d_outputs[t]
  48.             dW_hy += np.dot(self.hidden_states[t+1].T, dy)
  49.             db_y += dy
  50.             
  51.             # 隐藏层的梯度
  52.             dh = np.dot(dy, self.W_hy.T) + dh_next
  53.             dh_raw = (1 - self.hidden_states[t+1] ** 2) * dh
  54.             
  55.             dW_xh += np.dot(self.inputs[t].T, dh_raw)
  56.             dW_hh += np.dot(self.hidden_states[t].T, dh_raw)
  57.             db_h += dh_raw
  58.             dh_next = np.dot(dh_raw, self.W_hh.T)
  59.             
  60.         # 更新参数
  61.         self.W_xh -= learning_rate * dW_xh
  62.         self.W_hh -= learning_rate * dW_hh
  63.         self.W_hy -= learning_rate * dW_hy
  64.         self.b_h -= learning_rate * db_h
  65.         self.b_y -= learning_rate * db_y
复制代码
在自然语言处理中,它可以用于实现底子的语言模型我们可以练习网络预测句子中的下一个词:
  1. def create_language_model():
  2.     vocab_size = 5000  # 词汇表大小
  3.     embedding_size = 128
  4.     hidden_size = 256
  5.    
  6.     model = SimpleRNN(embedding_size, hidden_size, vocab_size)
  7.     return model
  8. def train_language_model(model, sentences, word_to_idx):
  9.     for sentence in sentences:
  10.         # 将句子转换为词嵌入序列
  11.         input_sequence = [word_to_embedding[word_to_idx[word]]
  12.                          for word in sentence[:-1]]
  13.         target_sequence = [word_to_idx[word] for word in sentence[1:]]
  14.         
  15.         # 前向传播
  16.         outputs = model.forward(input_sequence)
  17.         
  18.         # 计算损失和梯度
  19.         d_outputs = []
  20.         for t, output in enumerate(outputs):
  21.             target = np.zeros((1, vocab_size))
  22.             target[0, target_sequence[t]] = 1
  23.             d_outputs.append(output - target)
  24.         
  25.         # 反向传播
  26.         model.backward(d_outputs)
复制代码
在时间序列预测范畴,简单循环网络可以用于预测股票价格、天气等连续值:
  1. def time_series_prediction(data, sequence_length):
  2.     model = SimpleRNN(input_size=1, hidden_size=32, output_size=1)
  3.    
  4.     # 准备训练数据
  5.     sequences = []
  6.     targets = []
  7.     for i in range(len(data) - sequence_length):
  8.         sequences.append(data[i:i+sequence_length])
  9.         targets.append(data[i+sequence_length])
  10.    
  11.     # 训练模型
  12.     for epoch in range(num_epochs):
  13.         for seq, target in zip(sequences, targets):
  14.             outputs = model.forward(seq)
  15.             d_outputs = [output - target for output in outputs]
  16.             model.backward(d_outputs)
复制代码
虽然简单循环网络在这些应用中表现出了一定的本领,但它也存在显着的局限性。重要问题包括:
   

  • 梯度消散和爆炸:在反向流传过程中,梯度会随着时间步的增加而衰减或爆炸。
  • 长程依靠问题:网络难以捕捉间隔较远的依靠关系。
  • 信息瓶颈:全部汗青信息都必要压缩在固定大小的隐藏状态中。
  为了克服这些限制,后来发展出了LSTM和GRU等更复杂的RNN变体。但是,理解简单循环网络的原理和实现对于掌握这些高级模型仍然是须要的。
2. 简单循环网络及其应用

简单循环神经网络(Simple RNN)是循环神经网络家属中最底子的架构。它通过在传统神经网络的底子上引入循环毗连,使网络具备了处理序列数据的本领。这种设计理念源于对人类认知过程的模拟:当我们阅读文本或听音乐时,总是会结合之前的内容来理解当前信息。简单循环网络正是通过这种方式,在处理序列数据的每个时间步都保持并更新一个内部状态,从而捕捉序列中的时序依靠关系。
从布局上看,简单循环网络的核心是循环层,它在每个时间步都执行雷同的运算。具体来说,网络在处理当前输入时,会同时考虑两个因素:当前时间步的输入数据和上一时间步的隐藏状态。这两部分信息通过权重矩阵进行加权组合,然后经过非线性激活函数(通常是tanh或ReLU)得到当前时间步的新隐藏状态。
这个过程可以用数学表达式表示为:h_t = tanh(W_xh * x_t + W_hh * h_{t-1} + b_h),此中W_xh是输入到隐藏层的权重矩阵,W_hh是隐藏层到隐藏层的权重矩阵,b_h是偏置项。
在练习过程中,简单循环网络接纳随时间反向流传(BPTT)算法。这种算法将网络在时间维度上睁开,转化为一个深度前馈网络,然后应用标准的反向流传算法进行练习。值得注意的是,由于全部时间步共享雷同的权重,网络的参数更新必要累积全部时间步的梯度。这种练习方式虽然直观,但在处理长序列时轻易出现梯度消散或梯度爆炸的问题。
然而,简单循环网络也存在一些固有的局限性。最显著的问题是长程依靠问题,即网络难以捕捉序列中相距较远的元素之间的关系。这个问题的根源在于,随着序列长度的增加,早期的信息会在多次非线性变换中渐渐减弱,最终大概完全丧失。别的,简单循环网络还面临着练习不稳固的问题,特殊是在处理长序列时,梯度的流传轻易出现消散或爆炸。
为了提升模型性能,我们可以采取一些实用的战略。合适的权重初始化,可以使用正交初始化或者Xavier/He初始化方法来减缓梯度问题。使用梯度裁剪技能,防止梯度爆炸导致的练习不稳固。在优化器的选择上,Adam或RMSprop等自顺应优化算法通常可以或许取得较好的效果。别的,批归一化等技能也可以帮助稳固练习过程。
在数据预处理方面,必要特殊注意序列长度的处理。由于实际应用中的序列每每长度不一,我们通常必要通过截断或添补的方式将它们处理成固定长度。对输入数据进行得当的标准化或归一化处理也是提升模型性能的重要步骤。
尽管简单循环网络存在这些局限性,但它的设计思想启发了后续更复杂的RNN变体,如长短期影象网络(LSTM)和门控循环单位(GRU)的发展。这些高级模型通过引入门控机制等创新设计,在很大程度上克服了简单循环网络的缺点,但其基本原理仍然源于简单循环网络的核心思想。
简单循环网络(Simple RNN)是最底子的RNN布局。在每个时间步,网络会:
   

  • 吸收当前时间步的输入
  • 结合上一时间步的隐藏状态
  • 通过非线性激活函数盘算当前时间步的隐藏状态
  • 输出预测结果
  这种布局可以应用于多种机器学习使命,比如序列预测、序列标注等。在情感分析使命中,我们可以这样实现:
  1. class SimpleRNN:
  2.     def __init__(self, input_size, hidden_size, output_size):
  3.         self.hidden_size = hidden_size
  4.         # 初始化权重
  5.         self.W_xh = np.random.randn(input_size, hidden_size) / np.sqrt(input_size)
  6.         self.W_hh = np.random.randn(hidden_size, hidden_size) / np.sqrt(hidden_size)
  7.         self.W_hy = np.random.randn(hidden_size, output_size) / np.sqrt(hidden_size)
  8.         
  9.     def forward(self, inputs):
  10.         h = np.zeros((1, self.hidden_size))
  11.         for x in inputs:
  12.             h = np.tanh(np.dot(x, self.W_xh) + np.dot(h, self.W_hh))
  13.         return np.dot(h, self.W_hy)
复制代码
3. 参数学习与优化

参数学习是循环神经网络中最核心的环节,它直接决定了模型的性能。与传统神经网络相比,RNN的参数学习具有其特殊性,这重要源于其处理序列数据的特性。让我们深入探究RNN的参数学习机制和优化战略。
随时间反向流传(BPTT)是RNN参数学习的底子算法。在前向流传过程中,RNN会按时间顺序处理输入序列,并在每个时间步保存须要的中间状态。当到达序列末尾时,网络管帐算损失函数,然后开始反向流传过程。这个过程可以通过下面的数学表达式来描述:

对于时间步t的前向流传:

通过代码来具体展示这个过程:
  1. class RNNWithOptimization:
  2.     def __init__(self, input_size, hidden_size, output_size):
  3.         # 初始化网络参数
  4.         self.params = {
  5.             'W_xh': np.random.randn(input_size, hidden_size) / np.sqrt(input_size),
  6.             'W_hh': np.random.randn(hidden_size, hidden_size) / np.sqrt(hidden_size),
  7.             'W_hy': np.random.randn(hidden_size, output_size) / np.sqrt(hidden_size),
  8.             'b_h': np.zeros((1, hidden_size)),
  9.             'b_y': np.zeros((1, output_size))
  10.         }
  11.         
  12.         # 初始化Adam优化器的动量参数
  13.         self.m = {key: np.zeros_like(value) for key, value in self.params.items()}
  14.         self.v = {key: np.zeros_like(value) for key, value in self.params.items()}
  15.         self.t = 0
  16.         
  17.     def forward_pass(self, inputs, targets):
  18.         """前向传播并计算损失"""
  19.         h = np.zeros((1, self.params['W_hh'].shape[0]))  # 初始化隐藏状态
  20.         loss = 0
  21.         cache = {'h': [h], 'y': [], 'inputs': inputs}
  22.         
  23.         # 前向传播through time
  24.         for t, x in enumerate(inputs):
  25.             # 计算隐藏状态
  26.             h = np.tanh(np.dot(x, self.params['W_xh']) +
  27.                        np.dot(h, self.params['W_hh']) +
  28.                        self.params['b_h'])
  29.             
  30.             # 计算输出
  31.             y = np.dot(h, self.params['W_hy']) + self.params['b_y']
  32.             
  33.             # 保存中间状态用于反向传播
  34.             cache['h'].append(h)
  35.             cache['y'].append(y)
  36.             
  37.             # 计算损失
  38.             loss += 0.5 * np.sum((y - targets[t]) ** 2)
  39.             
  40.         return loss, cache
  41.    
  42.     def backward_pass(self, cache, targets, clip_threshold=5):
  43.         """实现BPTT算法"""
  44.         grads = {key: np.zeros_like(value) for key, value in self.params.items()}
  45.         H = len(cache['h']) - 1  # 序列长度
  46.         
  47.         dh_next = np.zeros_like(cache['h'][0])
  48.         
  49.         for t in reversed(range(H)):
  50.             # 计算输出层的梯度
  51.             dy = cache['y'][t] - targets[t]
  52.             grads['W_hy'] += np.dot(cache['h'][t+1].T, dy)
  53.             grads['b_y'] += dy
  54.             
  55.             # 反向传播到隐藏层
  56.             dh = np.dot(dy, self.params['W_hy'].T) + dh_next
  57.             
  58.             # 计算tanh的梯度
  59.             dtanh = (1 - cache['h'][t+1] ** 2) * dh
  60.             
  61.             # 计算各参数的梯度
  62.             grads['b_h'] += dtanh
  63.             grads['W_xh'] += np.dot(cache['inputs'][t].T, dtanh)
  64.             grads['W_hh'] += np.dot(cache['h'][t].T, dtanh)
  65.             
  66.             # 为下一个时间步准备梯度
  67.             dh_next = np.dot(dtanh, self.params['W_hh'].T)
  68.         
  69.         # 梯度裁剪
  70.         for key in grads:
  71.             np.clip(grads[key], -clip_threshold, clip_threshold, out=grads[key])
  72.             
  73.         return grads
  74.    
  75.     def adam_optimize(self, grads, learning_rate=0.001, beta1=0.9, beta2=0.999, epsilon=1e-8):
  76.         """实现Adam优化算法"""
  77.         self.t += 1
  78.         
  79.         for key in self.params:
  80.             # 更新动量
  81.             self.m[key] = beta1 * self.m[key] + (1 - beta1) * grads[key]
  82.             self.v[key] = beta2 * self.v[key] + (1 - beta2) * (grads[key] ** 2)
  83.             
  84.             # 偏差修正
  85.             m_hat = self.m[key] / (1 - beta1 ** self.t)
  86.             v_hat = self.v[key] / (1 - beta2 ** self.t)
  87.             
  88.             # 更新参数
  89.             self.params[key] -= learning_rate * m_hat / (np.sqrt(v_hat) + epsilon)
复制代码
在实际应用中,RNN的练习还必要考虑以下几个关键优化战略:
   

  • 梯度裁剪:防止梯度爆炸问题,通过设置梯度阈值来限制梯度的大小:
  1. def clip_gradients(gradients, threshold=5.0):
  2.     for grad in gradients.values():
  3.         np.clip(grad, -threshold, threshold, out=grad)
复制代码
  

  •  学习率调整:接纳学习率衰减或自顺应学习率战略:
  1. def adjust_learning_rate(initial_lr, epoch, decay_rate=0.1):
  2.     return initial_lr / (1 + decay_rate * epoch)
复制代码
  

  • 正则化技能:包括权重衰减、dropout等:
  1. def apply_dropout(h, dropout_rate=0.5):
  2.     mask = (np.random.rand(*h.shape) > dropout_rate) / (1 - dropout_rate)
  3.     return h * mask
复制代码
  

  • 批量练习:使用小批量梯度下降来提高练习效率和稳固性:
  1. def batch_generator(data, batch_size):
  2.     n_batches = len(data) // batch_size
  3.     for i in range(n_batches):
  4.         yield data[i*batch_size:(i+1)*batch_size]
复制代码
  

  • 初始化战略:接纳得当的权重初始化方法:
  1. def xavier_initialization(input_dim, output_dim):
  2.     return np.random.randn(input_dim, output_dim) * np.sqrt(2.0/(input_dim + output_dim))
复制代码
为了更好地监控练习过程,我们还必要实现验证和早停机制
  1. class EarlyStopping:
  2.     def __init__(self, patience=5, min_delta=0):
  3.         self.patience = patience
  4.         self.min_delta = min_delta
  5.         self.counter = 0
  6.         self.best_loss = None
  7.         self.early_stop = False
  8.         
  9.     def __call__(self, val_loss):
  10.         if self.best_loss is None:
  11.             self.best_loss = val_loss
  12.         elif val_loss > self.best_loss - self.min_delta:
  13.             self.counter += 1
  14.             if self.counter >= self.patience:
  15.                 self.early_stop = True
  16.         else:
  17.             self.best_loss = val_loss
  18.             self.counter = 0
复制代码
在练习循环中,我们必要综合运用这些优化战略
  1. def train_rnn(model, train_data, val_data, epochs=100, batch_size=32):
  2.     early_stopping = EarlyStopping(patience=5)
  3.    
  4.     for epoch in range(epochs):
  5.         train_loss = 0
  6.         for batch in batch_generator(train_data, batch_size):
  7.             # 前向传播
  8.             loss, cache = model.forward_pass(batch.inputs, batch.targets)
  9.             
  10.             # 反向传播
  11.             grads = model.backward_pass(cache, batch.targets)
  12.             
  13.             # 应用优化策略
  14.             clip_gradients(grads)
  15.             model.adam_optimize(grads)
  16.             
  17.             train_loss += loss
  18.             
  19.         # 验证
  20.         val_loss = evaluate(model, val_data)
  21.         
  22.         # 早停检查
  23.         early_stopping(val_loss)
  24.         if early_stopping.early_stop:
  25.             print(f"Early stopping at epoch {epoch}")
  26.             break
复制代码
参数学习与优化是RNN成功应用的关键。通过公道的优化战略组合,我们可以显著提升模型的练习效果和泛化本领。在实践中,必要根据具体使命特点和数据特性,灵活调整这些优化战略的使用方式和参数设置。同时,良好的监控和调试机制也是确保练习过程顺利进行的重要保障。
4. 基于门控的循环神经网络

基于门控的循环神经网络是为了办理简单RNN在处理长序列时存在的梯度消散和长程依靠问题而提出的。通过引入门控机制,这些网络可以或许更好地控制信息的流动,从而在长序列处理使命中取得更好的效果。
4.1 长短期影象网络(LSTM)

LSTM是最早提出且最为经典的门控RNN布局。它通过设计忘记门、输入门和输出门三个门控单位,以及一个影象单位,来控制信息的存储、更新和输出。
4.1.1 LSTM的核心组件:

  1. class LSTM:
  2.     def __init__(self, input_size, hidden_size):
  3.         # 初始化权重矩阵
  4.         # 输入门参数
  5.         self.W_xi = np.random.randn(input_size, hidden_size) * 0.01
  6.         self.W_hi = np.random.randn(hidden_size, hidden_size) * 0.01
  7.         self.b_i = np.zeros((1, hidden_size))
  8.         
  9.         # 遗忘门参数
  10.         self.W_xf = np.random.randn(input_size, hidden_size) * 0.01
  11.         self.W_hf = np.random.randn(hidden_size, hidden_size) * 0.01
  12.         self.b_f = np.zeros((1, hidden_size))
  13.         
  14.         # 输出门参数
  15.         self.W_xo = np.random.randn(input_size, hidden_size) * 0.01
  16.         self.W_ho = np.random.randn(hidden_size, hidden_size) * 0.01
  17.         self.b_o = np.zeros((1, hidden_size))
  18.         
  19.         # 候选记忆单元参数
  20.         self.W_xc = np.random.randn(input_size, hidden_size) * 0.01
  21.         self.W_hc = np.random.randn(hidden_size, hidden_size) * 0.01
  22.         self.b_c = np.zeros((1, hidden_size))
  23.    
  24.     def forward(self, x, prev_h, prev_c):
  25.         # 输入门
  26.         i = sigmoid(np.dot(x, self.W_xi) + np.dot(prev_h, self.W_hi) + self.b_i)
  27.         
  28.         # 遗忘门
  29.         f = sigmoid(np.dot(x, self.W_xf) + np.dot(prev_h, self.W_hf) + self.b_f)
  30.         
  31.         # 输出门
  32.         o = sigmoid(np.dot(x, self.W_xo) + np.dot(prev_h, self.W_ho) + self.b_o)
  33.         
  34.         # 候选记忆单元
  35.         c_tilde = np.tanh(np.dot(x, self.W_xc) + np.dot(prev_h, self.W_hc) + self.b_c)
  36.         
  37.         # 更新记忆单元
  38.         c = f * prev_c + i * c_tilde
  39.         
  40.         # 计算隐藏状态
  41.         h = o * np.tanh(c)
  42.         
  43.         return h, c
复制代码
LSTM的各个门控单位作用如下:
   

  • 忘记门(f):控制上一时候影象单位中的信息有多少必要保留
  • 输入门(i):控制当前时候新信息有多少必要写入影象单位
  • 输出门(o):控制影象单位中的信息有多少必要输出到隐藏状态
  • 影象单位(c):存储长期影象,通过门控机制进行更新
  4.2 门控循环单位(GRU)

GRU是LSTM的简化版本,它将输入门和忘记门合并为更新门,并引入重置门来控制汗青信息的使用。
  1. class GRU:
  2.     def __init__(self, input_size, hidden_size):
  3.         # 更新门参数
  4.         self.W_xz = np.random.randn(input_size, hidden_size) * 0.01
  5.         self.W_hz = np.random.randn(hidden_size, hidden_size) * 0.01
  6.         self.b_z = np.zeros((1, hidden_size))
  7.         
  8.         # 重置门参数
  9.         self.W_xr = np.random.randn(input_size, hidden_size) * 0.01
  10.         self.W_hr = np.random.randn(hidden_size, hidden_size) * 0.01
  11.         self.b_r = np.zeros((1, hidden_size))
  12.         
  13.         # 候选隐藏状态参数
  14.         self.W_xh = np.random.randn(input_size, hidden_size) * 0.01
  15.         self.W_hh = np.random.randn(hidden_size, hidden_size) * 0.01
  16.         self.b_h = np.zeros((1, hidden_size))
  17.    
  18.     def forward(self, x, prev_h):
  19.         # 更新门
  20.         z = sigmoid(np.dot(x, self.W_xz) + np.dot(prev_h, self.W_hz) + self.b_z)
  21.         
  22.         # 重置门
  23.         r = sigmoid(np.dot(x, self.W_xr) + np.dot(prev_h, self.W_hr) + self.b_r)
  24.         
  25.         # 候选隐藏状态
  26.         h_tilde = np.tanh(np.dot(x, self.W_xh) + np.dot(r * prev_h, self.W_hh) + self.b_h)
  27.         
  28.         # 更新隐藏状态
  29.         h = (1 - z) * prev_h + z * h_tilde
  30.         
  31.         return h
复制代码
5 实际应用中的优化技巧

5.1 变体和改进

  1. class PeepholeConnLSTM:
  2.     def __init__(self, input_size, hidden_size):
  3.         # 标准LSTM参数
  4.         self.lstm = LSTM(input_size, hidden_size)
  5.         
  6.         # Peephole连接参数
  7.         self.W_ci = np.random.randn(hidden_size, hidden_size) * 0.01
  8.         self.W_cf = np.random.randn(hidden_size, hidden_size) * 0.01
  9.         self.W_co = np.random.randn(hidden_size, hidden_size) * 0.01
  10.    
  11.     def forward(self, x, prev_h, prev_c):
  12.         # 修改门控计算,加入记忆单元的直接连接
  13.         i = sigmoid(np.dot(x, self.lstm.W_xi) +
  14.                    np.dot(prev_h, self.lstm.W_hi) +
  15.                    np.dot(prev_c, self.W_ci) +
  16.                    self.lstm.b_i)
  17.         
  18.         f = sigmoid(np.dot(x, self.lstm.W_xf) +
  19.                    np.dot(prev_h, self.lstm.W_hf) +
  20.                    np.dot(prev_c, self.W_cf) +
  21.                    self.lstm.b_f)
  22.         
  23.         # 其余计算与标准LSTM相同
  24.         ...
复制代码
5.2 注意力机制的结合

  1. class AttentionLSTM:
  2.     def __init__(self, input_size, hidden_size, attention_size):
  3.         self.lstm = LSTM(input_size, hidden_size)
  4.         self.attention = Attention(hidden_size, attention_size)
  5.    
  6.     def forward(self, x_sequence, prev_h, prev_c):
  7.         # 存储所有隐藏状态
  8.         all_hidden_states = []
  9.         current_h, current_c = prev_h, prev_c
  10.         
  11.         # LSTM前向传播
  12.         for x in x_sequence:
  13.             current_h, current_c = self.lstm.forward(x, current_h, current_c)
  14.             all_hidden_states.append(current_h)
  15.         
  16.         # 计算注意力权重
  17.         context = self.attention(all_hidden_states)
  18.         
  19.         return context, current_h, current_c
复制代码
6 实现细节和最佳实践

6.1 初始化战略

  1. def initialize_lstm_params(input_size, hidden_size):
  2.     # 使用正交初始化
  3.     def orthogonal(shape):
  4.         rand = np.random.randn(*shape)
  5.         u, _, v = np.linalg.svd(rand)
  6.         return u if u.shape == shape else v
  7.    
  8.     params = {}
  9.     for gate in ['i', 'f', 'o', 'c']:
  10.         params[f'W_x{gate}'] = orthogonal((input_size, hidden_size))
  11.         params[f'W_h{gate}'] = orthogonal((hidden_size, hidden_size))
  12.         params[f'b_{gate}'] = np.zeros((1, hidden_size))
  13.         
  14.         # 特殊处理遗忘门偏置
  15.         if gate == 'f':
  16.             params[f'b_{gate}'] += 1.0
  17.    
  18.     return params
复制代码
6.1.1 梯度处理

  1. def lstm_backward(dh_next, dc_next, cache):
  2.     # 解包缓存的值
  3.     x, prev_h, prev_c, i, f, o, c_tilde, c, h = cache
  4.    
  5.     # 计算各个门和状态的梯度
  6.     do = dh_next * np.tanh(c)
  7.     dc = dc_next + dh_next * o * (1 - np.tanh(c)**2)
  8.    
  9.     di = dc * c_tilde
  10.     df = dc * prev_c
  11.     dc_tilde = dc * i
  12.    
  13.     # 计算激活函数的梯度
  14.     di_raw = di * i * (1 - i)
  15.     df_raw = df * f * (1 - f)
  16.     do_raw = do * o * (1 - o)
  17.     dc_tilde_raw = dc_tilde * (1 - c_tilde**2)
  18.    
  19.     # 计算权重梯度
  20.     dW_xi = np.dot(x.T, di_raw)
  21.     dW_hi = np.dot(prev_h.T, di_raw)
  22.     db_i = np.sum(di_raw, axis=0, keepdims=True)
  23.    
  24.     # ... 类似计算其他参数的梯度
  25.    
  26.     return dW_xi, dW_hi, db_i, ...
复制代码
基于门控的循环神经网络通过其特殊的布局设计,很好地办理了简单RNN面临的问题。它们在各种序列处理使命中都展现出了优异的性能,成为了深度学习范畴最重要的模型之一。理解这些模型的工作原理和实现细节,对于实际应用中选择合适的模型布局和优化战略具有重要的指导意义。
内容不全等,请各位理解支持!!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

守听

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表