当前位置: 首页 > news >正文

循环神经网络(RNN)Python实现详解

循环神经网络(Recurrent Neural Network, RNN)是一种专门处理序列数据的神经网络结构。本文将深入讲解RNN的Python实现,并在每段代码后提供相应的数学公式。

RNN基础理论

RNN的核心结构包含以下关键公式:

  1. 隐层状态更新公式
    st=g1(Uxt+Wst−1+ba)s_t = g_1(Ux_t + Ws_{t-1} + b_a)st=g1(Uxt+Wst1+ba)
    其中sts_tst是当前时刻隐层状态,xtx_txt是当前输入,st−1s_{t-1}st1是前一时刻隐层状态,UUUWWW是权重矩阵,bab_aba是偏置项。

  2. 输出层公式
    ot=g2(Vst+by)o_t = g_2(Vs_t + b_y)ot=g2(Vst+by)
    其中oto_tot是当前时刻输出,VVV是输出权重矩阵,byb_yby是输出偏置项。

  3. 初始状态
    s0=0s_0 = 0s0=0

Python完整代码

import numpy as npdef softmax(x):e_x = np.exp(x-np.max(x))return e_x / e_x.sum(axis=0)def rnn_cell_forword(x_t, s_prev, parameters):"""单个cell的前向传播过程:param x_t: 当前时刻序列输入:param s_prev: 上一个cell的隐层状态输入:param parameters: cell的参数:return: 隐层输出,s_next, out_pred, cache"""# 取出参数U = parameters["U"]W = parameters["W"]V = parameters["V"]ba = parameters["ba"]by = parameters["by"]# 隐层输出计算s_next = np.tanh(np.dot(U, x_t) + np.dot(W, s_prev) +ba)# 计算cell的输出out_pred = softmax(np.dot(V, s_next) + by)# 记录每一层的值,用于反向传播使用cache = (s_next, s_prev, x_t, parameters)return s_next, out_pred, cachedef rnn_forword(x, s0 ,parameters):"""对于所有cell进行前向传播:param x: 输入序列,形状(m, 1, T),T为序列长度:param s0: 初始状态输入,0:param parameters: 所以cell共享的参数 U,W,V,ba,by:return: s, y, caches"""caches = []# 获取序列的长度,时刻数m, _, T =x.shape# 获取输入的Nm, n = parameters["V"].shape# 获取s0的值, 保存到S_next里面s_next = s0# 定义s,y保留所有cell的隐层状态以及输出s = np.zeros((n, 1 ,T))y = np.zeros((m, 1, T))# 循环对每一个cell进行前向传播计算for t in range(T):# 对于t时刻的cell进行输出s_next, out_pred, cache = rnn_cell_forword(x[:, :, t], s_next, parameters)#放入数组当中s[:, :, t] = s_nexty[:, :, t] = out_pred# 放入所有的缓存到列表当中caches.append(cache)return s, y, cachesdef rnn_cell_backward(ds_next, cache):"""每个cell的右边输入梯度:param ds_next: s_next的梯度值:param cache: 当前cell的缓存:return: gradients"""# 获取cache当中的缓存值以及参数(s_next, s_prev, x_t, parameters) = cacheU = parameters["U"]W = parameters["W"]V = parameters["V"]ba = parameters["ba"]by = parameters["by"]# 根据公式进行反向传播计算# 1. 计算tanh的导数dtanh = (1 - s_next ** 2) * ds_next# 2. 计算U的梯度值dU = np.dot(dtanh, x_t.T)# 3. 计算W的梯度值dW = np.dot(dtanh, s_prev.T)# 4.计算ba的梯度值dba = np.sum(dtanh, axis=1, keepdims=1)# 5.计算x_t的导数dx_t = np.dot(U.T, dtanh)# 6.计算s_prev的导数ds_prev = np.dot(W.T, dtanh)# 把所有的导数保存到字典中gradients = {"dtanh":dtanh, "dU":dU, "dW":dW, "dba":dba, "dx_t":dx_t, "ds_prev":ds_prev}return gradientsdef rnn_backward(ds, caches):"""所有的cell的反向传播过程:param ds: 每个时刻的损失对于s的梯度值:param caches: 每个cell的输出值:return:"""# 取出caches当中的值(s1, s0, x_1, parameters) = caches[0]# 获取输入数据的总序列长度n, _, T = ds.shapem, _ = x_1.shape# 存储所以一次更新后的参数的梯度dU = np.zeros((n, m))dW = np.zeros((n, n))dba = np.zeros((n, 1))# 初始化一个为0的s第二部分梯度值ds_prevt = np.zeros((n, 1))# 保存其他不需要更新的梯度dx = np.zeros((m, 1, T))#循环从后往前进行计算梯度for t in reversed(range(T)):# 从三时刻开始# 2,1,0时刻的s梯度由两个部分组成gradients = rnn_cell_backward(ds[:, :, t] + ds_prevt, caches[t])ds_prevt = gradients["ds_prev"]# 共享梯度相加dU += gradients["dU"]dW += gradients["dW"]dba += gradients["dba"]# 保存每一层的x_t,s_prev的梯度值dx[:, :, t] = gradients["dx_t"]# 返回所有更新参数的梯度以及其他变量的梯度值gradients = {"dU":dU, "dW":dW, "dba":dba, "dx":dx}return gradientsif __name__ == '__main__':np.random.seed(1)# 定义四个cell, 每一个形状(3,1)x = np.random.randn(3, 1, 4)s0 = np.random.randn(5, 1)U = np.random.randn(5, 3)W = np.random.randn(5, 5)V = np.random.randn(3, 5)ba = np.random.randn(5, 1)by = np.random.randn(3, 1)parameters = {"U":U, "W":W, "V":V, "ba":ba, "by":by}s, y, caches = rnn_forword(x, s0, parameters)# 随机给每4个cell的隐藏层输出的导数结果(实际需要计算)ds = np.random.randn(5, 1, 4)gradients = rnn_backward(ds, caches)print(gradients)

Python实现详解

1. Softmax函数实现

def softmax(x):e_x = np.exp(x - np.max(x))return e_x / e_x.sum(axis=0)

数学公式
softmax(xi)=exi−max⁡(x)∑jexj−max⁡(x)\text{softmax}(x_i) = \frac{e^{x_i - \max(x)}}{\sum_j e^{x_j - \max(x)}}softmax(xi)=jexjmax(x)eximax(x)
其中减去最大值是为了数值稳定性,防止指数计算溢出。

2. 单个RNN单元前向传播

def rnn_cell_forward(x_t, s_prev, parameters):U = parameters["U"]W = parameters["W"]V = parameters["V"]ba = parameters["ba"]by = parameters["by"]s_next = np.tanh(np.dot(U, x_t) + np.dot(W, s_prev) + ba)out_pred = softmax(np.dot(V, s_next) + by)cache = (s_next, s_prev, x_t, parameters)return s_next, out_pred, cache

数学公式
st=tanh⁡(Uxt+Wst−1+ba)s_t = \tanh(Ux_t + Ws_{t-1} + b_a)st=tanh(Uxt+Wst1+ba)
ot=softmax(Vst+by)o_t = \text{softmax}(Vs_t + b_y)ot=softmax(Vst+by)
其中:

  • tanh⁡\tanhtanh是激活函数
  • sts_tst是当前隐层状态
  • oto_tot是当前输出预测

3. 完整RNN前向传播

def rnn_forward(x, s0, parameters):caches = []m, _, T = x.shapem, n = parameters["V"].shapes_next = s0s = np.zeros((n, 1, T))y = np.zeros((m, 1, T))for t in range(T):s_next, out_pred, cache = rnn_cell_forward(x[:, :, t], s_next, parameters)s[:, :, t] = s_nexty[:, :, t] = out_predcaches.append(cache)return s, y, caches

数学过程

  1. 初始化隐层状态 s0s_0s0
  2. 对每个时间步 t∈[0,T−1]t \in [0, T-1]t[0,T1]:
    • 计算 st=tanh⁡(Uxt+Wst−1+ba)s_t = \tanh(Ux_t + Ws_{t-1} + b_a)st=tanh(Uxt+Wst1+ba)
    • 计算 ot=softmax(Vst+by)o_t = \text{softmax}(Vs_t + b_y)ot=softmax(Vst+by)
  3. 存储所有时间步的隐层状态和输出

4. 单个RNN单元反向传播

def rnn_cell_backward(ds_next, cache):s_next, s_prev, x_t, parameters = cacheU, W, V, ba, by = parameters["U"], parameters["W"], parameters["V"], parameters["ba"], parameters["by"]dtanh = (1 - s_next**2) * ds_nextdU = np.dot(dtanh, x_t.T)dW = np.dot(dtanh, s_prev.T)dba = np.sum(dtanh, axis=1, keepdims=True)dx_t = np.dot(U.T, dtanh)ds_prev = np.dot(W.T, dtanh)gradients = {"dU": dU, "dW": dW, "dba": dba, "dx_t": dx_t, "ds_prev": ds_prev}return gradients

数学推导

  1. tanh⁡\tanhtanh 激活函数的导数:
    ∂tanh⁡(z)∂z=1−tanh⁡2(z)\frac{\partial \tanh(z)}{\partial z} = 1 - \tanh^2(z)ztanh(z)=1tanh2(z)
    因此:
    dtanh⁡=(1−st2)⊙dsnextd_{\tanh} = (1 - s_t^2) \odot ds_{\text{next}}dtanh=(1st2)dsnext

  2. 参数梯度计算:
    ∂L∂U=dtanh⁡⋅xtT\frac{\partial L}{\partial U} = d_{\tanh} \cdot x_t^TUL=dtanhxtT
    ∂L∂W=dtanh⁡⋅st−1T\frac{\partial L}{\partial W} = d_{\tanh} \cdot s_{t-1}^TWL=dtanhst1T
    ∂L∂ba=∑dtanh⁡(沿batch维度求和)\frac{\partial L}{\partial b_a} = \sum d_{\tanh} \quad (\text{沿batch维度求和})baL=dtanh(沿batch维度求和)

  3. 输入和前一状态梯度:
    ∂L∂xt=UT⋅dtanh⁡\frac{\partial L}{\partial x_t} = U^T \cdot d_{\tanh}xtL=UTdtanh
    ∂L∂st−1=WT⋅dtanh⁡\frac{\partial L}{\partial s_{t-1}} = W^T \cdot d_{\tanh}st1L=WTdtanh

5. 完整RNN反向传播

def rnn_backward(ds, caches):s1, s0, x_1, parameters = caches[0]n, _, T = ds.shapem, _ = x_1.shapedU = np.zeros((n, m))dW = np.zeros((n, n))dba = np.zeros((n, 1))ds_prevt = np.zeros((n, 1))dx = np.zeros((m, 1, T))for t in reversed(range(T)):gradients = rnn_cell_backward(ds[:, :, t] + ds_prevt, caches[t])ds_prevt = gradients["ds_prev"]dU += gradients["dU"]dW += gradients["dW"]dba += gradients["dba"]dx[:, :, t] = gradients["dx_t"]gradients = {"dU": dU, "dW": dW, "dba": dba, "dx": dx}return gradients

数学过程

  1. 初始化参数梯度为零
  2. 从最后一个时间步向前遍历(时间反向传播):
    t=T−1,T−2,…,0t = T-1, T-2, \dots, 0t=T1,T2,,0
  3. 每个时间步的梯度包含两部分:
    • 当前时刻输出的梯度 dstds_tdst
    • 下一时刻传递的梯度 dsprevtds_{\text{prevt}}dsprevt
  4. 参数梯度累加(参数共享):
    ∂L∂U=∑t=0T−1∂L∂Ut\frac{\partial L}{\partial U} = \sum_{t=0}^{T-1} \frac{\partial L}{\partial U_t}UL=t=0T1UtL
    ∂L∂W=∑t=0T−1∂L∂Wt\frac{\partial L}{\partial W} = \sum_{t=0}^{T-1} \frac{\partial L}{\partial W_t}WL=t=0T1WtL
    ∂L∂ba=∑t=0T−1∂L∂ba,t\frac{\partial L}{\partial b_a} = \sum_{t=0}^{T-1} \frac{\partial L}{\partial b_{a,t}}baL=t=0T1ba,tL

6. 测试代码

if __name__ == '__main__':np.random.seed(1)# 创建模拟数据:4个时间步,每个输入3维x = np.random.randn(3, 1, 4)s0 = np.random.randn(5, 1)  # 初始隐层状态# 初始化参数U = np.random.randn(5, 3)   # 输入到隐层权重W = np.random.randn(5, 5)   # 隐层到隐层权重V = np.random.randn(3, 5)   # 隐层到输出权重ba = np.random.randn(5, 1)  # 隐层偏置by = np.random.randn(3, 1)  # 输出偏置parameters = {"U": U, "W": W, "V": V, "ba": ba, "by": by}# 前向传播s, y, caches = rnn_forward(x, s0, parameters)# 模拟梯度(实际应用中来自损失函数)ds = np.random.randn(5, 1, 4)# 反向传播gradients = rnn_backward(ds, caches)print("梯度字典:")for key in gradients:print(f"{key}: {gradients[key].shape}")

RNN的特点与挑战

优点:

  1. 处理变长序列:适合文本、语音等序列数据
  2. 参数共享:所有时间步共享同一组参数
  3. 记忆能力:隐层状态可携带历史信息

挑战:

  1. 梯度消失/爆炸:长序列训练困难
    ∂st∂sk=∏i=kt−1∂si+1∂si\frac{\partial s_t}{\partial s_k} = \prod_{i=k}^{t-1} \frac{\partial s_{i+1}}{\partial s_i}skst=i=kt1sisi+1
    当序列较长时,梯度可能指数级衰减或爆炸

  2. 短期记忆限制:难以捕获长期依赖关系

总结

本文详细讲解了RNN的Python实现,包括:

  1. 前向传播过程(单个单元和完整序列)
  2. 反向传播过程(BPTT算法)
  3. 核心数学公式推导
  4. 完整可运行代码示例

RNN是处理序列数据的基础模型,理解其原理和实现对于学习更高级的序列模型(如LSTM、GRU)至关重要。实际应用中,我们通常使用这些改进型来解决标准RNN的梯度问题,但标准RNN仍然是理解循环神经网络的基础。

http://www.dtcms.com/a/272012.html

相关文章:

  • 什么是VR实景漫游?VR实景的制作办法?
  • VR博物馆:概念与内涵
  • 广州华锐互动在各领域打造的 VR 成功案例展示​
  • 数字孪生技术引领UI前端设计新趋势:增强现实与虚拟现实的融合应用
  • VBA即用型代码手册:Range对象 Range Object
  • vue3 uniapp 使用ref更新值后子组件没有更新 ref reactive的区别?使用from from -item执行表单验证一直提示没有值
  • 软考(软件设计师)计算机网络-物理层,数据链路层
  • QT - Qvector用法
  • Java设计模式之行为型模式(观察者模式)介绍与说明
  • 关于k8s Kubernetes的10个面试题
  • 【AXI】读重排序深度
  • Scala实现网页数据采集示例
  • linux的用户和权限(学习笔记
  • 西门子200SMART如何无线联三菱FX3U?御控工业网关实现多站点PLC无线通讯集中控制!
  • MiniGPT4源码拆解——models
  • 膨胀卷积介绍
  • QPC框架中状态机的设计优势和特殊之处
  • 大模型在膀胱癌诊疗全流程预测及应用研究报告
  • 【Linux基础命令使用】VIM编辑器的使用
  • 【个人笔记】负载均衡
  • Linux小白学习基础内容
  • LUMP+NFS架构的Discuz论坛部署
  • 可视化DIY小程序工具!开源拖拽式源码系统,自由搭建,完整的源代码包分享
  • Spring Boot 3.4 :@Fallback 注解 - 让微服务容错更简单
  • 分桶表的介绍和作用
  • OpenSearch 视频 RAG 实践
  • GO 启动 简单服务
  • 【YOLO脚本】yolo格式数据集删除缺失删除图片和标签
  • 青岛门卫事件后:高温晕厥救援技术突破
  • 文件系统----底层架构