多层 RNN原理以及实现

数学原理


多层 RNN 的核心思想是堆叠多个 RNN 层,每一层的输出作为下一层的输入,从而逐层提取更高层次的抽象特征。


1. 单层 RNN 的数学表示

首先,单层 RNN 的计算过程如下。对于一个时间步 t t t,单层 RNN 的隐藏状态 h t h_t ht 和输出 y t y_t yt 可以表示为:

h t = activation ( W i h x t + b i h + W h h h t − 1 + b h h ) h_t = \text{activation}(W_{ih} x_t + b_{ih} + W_{hh} h_{t-1} + b_{hh}) ht=activation(Wihxt+bih+Whhht1+bhh)
y t = W h o h t + b h o y_t = W_{ho} h_t + b_{ho} yt=Whoht+bho

其中:

  • x t x_t xt 是时间步 t t t 的输入。
  • h t h_t ht 是时间步 t t t 的隐藏状态。
  • h t − 1 h_{t-1} ht1 是时间步 t − 1 t-1 t1 的隐藏状态。
  • W i h W_{ih} Wih W h h W_{hh} Whh W h o W_{ho} Who 是权重矩阵。
  • b i h b_{ih} bih b h h b_{hh} bhh b h o b_{ho} bho 是偏置项。
  • activation \text{activation} activation 是激活函数(如 tanh ⁡ \tanh tanh ReLU \text{ReLU} ReLU)。

2. 多层 RNN 的数学表示

假设我们有一个 L L L 层的 RNN,每一层的隐藏状态为 h t ( l ) h_t^{(l)} ht(l),其中 l l l 表示第 l l l 层, t t t 表示时间步。多层 RNN 的计算过程如下:

(1) 第一层( l = 1 l = 1 l=1

第一层的输入是原始输入序列 x t x_t xt,隐藏状态 h t ( 1 ) h_t^{(1)} ht(1) 的计算公式为:

h t ( 1 ) = activation ( W i h ( 1 ) x t + b i h ( 1 ) + W h h ( 1 ) h t − 1 ( 1 ) + b h h ( 1 ) ) h_t^{(1)} = \text{activation}(W_{ih}^{(1)} x_t + b_{ih}^{(1)} + W_{hh}^{(1)} h_{t-1}^{(1)} + b_{hh}^{(1)}) ht(1)=activation(Wih(1)xt+bih(1)+Whh(1)ht1(1)+bhh(1))

其中:

  • W i h ( 1 ) W_{ih}^{(1)} Wih(1) W h h ( 1 ) W_{hh}^{(1)} Whh(1) 是第一层的权重矩阵。
  • b i h ( 1 ) b_{ih}^{(1)} bih(1) b h h ( 1 ) b_{hh}^{(1)} bhh(1) 是第一层的偏置项。
(2) 第 l l l 层( l > 1 l > 1 l>1

l l l 层的输入是第 l − 1 l-1 l1 层的输出 h t ( l − 1 ) h_t^{(l-1)} ht(l1),隐藏状态 h t ( l ) h_t^{(l)} ht(l) 的计算公式为:

h t ( l ) = activation ( W i h ( l ) h t ( l − 1 ) + b i h ( l ) + W h h ( l ) h t − 1 ( l ) + b h h ( l ) ) h_t^{(l)} = \text{activation}(W_{ih}^{(l)} h_t^{(l-1)} + b_{ih}^{(l)} + W_{hh}^{(l)} h_{t-1}^{(l)} + b_{hh}^{(l)}) ht(l)=activation(Wih(l)ht(l1)+bih(l)+Whh(l)ht1(l)+bhh(l))

其中:

  • W i h ( l ) W_{ih}^{(l)} Wih(l) W h h ( l ) W_{hh}^{(l)} Whh(l) 是第 l l l 层的权重矩阵。
  • b i h ( l ) b_{ih}^{(l)} bih(l) b h h ( l ) b_{hh}^{(l)} bhh(l) 是第 l l l 层的偏置项。
(3) 输出层

最后一层(第 L L L 层)的输出 h t ( L ) h_t^{(L)} ht(L) 作为整个网络的输出 y t y_t yt

y t = W h o h t ( L ) + b h o y_t = W_{ho} h_t^{(L)} + b_{ho} yt=Whoht(L)+bho

其中:

  • W h o W_{ho} Who b h o b_{ho} bho 是输出层的权重矩阵和偏置项。

3. 多层 RNN 的数据流向

以下是一个 L L L 层 RNN 的数据流向的数学描述:

(1) 输入序列

输入序列为 x 1 , x 2 , … , x T x_1, x_2, \dots, x_T x1,x2,,xT,其中 T T T 是序列长度。

(2) 初始化隐藏状态

每一层的初始隐藏状态 h 0 ( l ) h_0^{(l)} h0(l) 通常初始化为零或随机值:

h 0 ( l ) = 0 或 h 0 ( l ) ∼ N ( 0 , σ 2 ) h_0^{(l)} = \mathbf{0} \quad \text{或} \quad h_0^{(l)} \sim \mathcal{N}(0, \sigma^2) h0(l)=0h0(l)N(0,σ2)

(3) 时间步 t t t 的计算

对于每个时间步 t t t,从第一层到第 L L L 层依次计算隐藏状态:

  1. 第一层
    h t ( 1 ) = activation ( W i h ( 1 ) x t + b i h ( 1 ) + W h h ( 1 ) h t − 1 ( 1 ) + b h h ( 1 ) ) h_t^{(1)} = \text{activation}(W_{ih}^{(1)} x_t + b_{ih}^{(1)} + W_{hh}^{(1)} h_{t-1}^{(1)} + b_{hh}^{(1)}) ht(1)=activation(Wih(1)xt+bih(1)+Whh(1)ht1(1)+bhh(1))

  2. l l l 层( l > 1 l > 1 l>1
    h t ( l ) = activation ( W i h ( l ) h t ( l − 1 ) + b i h ( l ) + W h h ( l ) h t − 1 ( l ) + b h h ( l ) ) h_t^{(l)} = \text{activation}(W_{ih}^{(l)} h_t^{(l-1)} + b_{ih}^{(l)} + W_{hh}^{(l)} h_{t-1}^{(l)} + b_{hh}^{(l)}) ht(l)=activation(Wih(l)ht(l1)+bih(l)+Whh(l)ht1(l)+bhh(l))

  3. 输出
    y t = W h o h t ( L ) + b h o y_t = W_{ho} h_t^{(L)} + b_{ho} yt=Whoht(L)+bho

(4) 序列输出

最终,整个序列的输出为 y 1 , y 2 , … , y T y_1, y_2, \dots, y_T y1,y2,,yT


4. 多层 RNN 的特点

(1) 逐层抽象
  • 每一层 RNN 可以看作是对输入序列的不同层次的抽象。
  • 较低层捕捉局部和细节信息,较高层捕捉全局和语义信息。
(2) 参数共享
  • 每一层的参数(权重矩阵和偏置项)在时间步之间共享。
  • 不同层的参数是独立的。
(3) 梯度传播
  • 在反向传播时,梯度会通过时间步和层数传播。
  • 由于梯度消失或爆炸问题,训练深层 RNN 可能会比较困难。

可视化原理

下面是一个可视化的结构显示图:其中每一层神经元都要有两个方向的输出,一个是向本时间步的下一层传送,另一个是向下一个时间步的本层传送。而且,每一个神经元都有两个权重矩阵。注意:下方右图仅仅是逻辑上展开的数据流,其中不同世间步上的同一层,用的是同一个权重矩阵。

在这里插入图片描述

代码实现


1. 示例任务

假设有一个简单的任务:

  • 处理一个长度为 4 的序列
  • 批次大小为 2
  • 每个时间步的输入特征维度为 3
  • 希望使用一个 2 层的单向 RNN
  • 隐藏状态维度为 5。

2. 输入数据

输入句子
  • 句子 1: “I love PyTorch”
  • 句子 2: “RNN is fun”
输入数据的形状
  • 序列长度 (seq_len): 4(假设每个单词是一个时间步)
  • 批次大小 (batch_size): 2
  • 输入特征维度 (input_size): 3(假设每个单词用一个 3 维向量表示)
具体输入数据
import torch# 输入数据形状: (seq_len, batch_size, input_size)
input_data = torch.tensor([# 时间步 1[[0.1, 0.2, 0.3],  # 句子 1 的第一个单词[0.4, 0.5, 0.6]], # 句子 2 的第一个单词# 时间步 2[[0.7, 0.8, 0.9],  # 句子 1 的第二个单词[1.0, 1.1, 1.2]], # 句子 2 的第二个单词# 时间步 3[[1.3, 1.4, 1.5],  # 句子 1 的第三个单词[1.6, 1.7, 1.8]], # 句子 2 的第三个单词# 时间步 4[[1.9, 2.0, 2.1],  # 句子 1 的第四个单词[2.2, 2.3, 2.4]]  # 句子 2 的第四个单词
])
print("Input shape:", input_data.shape)  # 输出: torch.Size([4, 2, 3])

3. 初始隐藏状态

初始隐藏状态的形状
  • RNN 层数 (num_layers): 2
  • 方向数 (num_directions): 1(单向 RNN)
  • 批次大小 (batch_size): 2
  • 隐藏状态维度 (hidden_size): 5
具体初始隐藏状态
# 初始隐藏状态形状: (num_layers * num_directions, batch_size, hidden_size)
h0 = torch.zeros(2, 2, 5)  # 2层RNN,批次大小为2,隐藏状态维度为5
print("h0 shape:", h0.shape)  # 输出: torch.Size([2, 2, 5])

4. 定义 RNN 模型

import torch.nn as nn# 定义 RNN
rnn = nn.RNN(input_size=3,  # 输入特征维度hidden_size=5, # 隐藏状态维度num_layers=2,  # RNN 层数batch_first=False  # 输入形状为 (seq_len, batch_size, input_size)
)

5. 前向传播

计算输出
# 前向传播
output, hn = rnn(input_data, h0)print("Output shape:", output.shape)  # 输出: torch.Size([4, 2, 5])
print("hn shape:", hn.shape)          # 输出: torch.Size([2, 2, 5])
输出解析
  1. output:

    • 形状为 (seq_len, batch_size, hidden_size),即 (4, 2, 5)
    • 包含了每个时间步的隐藏状态。
    • 例如,output[0] 是第一个时间步的隐藏状态,output[-1] 是最后一个时间步的隐藏状态。
  2. hn:

    • 形状为 (num_layers, batch_size, hidden_size),即 (2, 2, 5)
    • 包含了最后一个时间步的隐藏状态。
    • 例如,hn[0] 是第一层的最终隐藏状态,hn[1] 是第二层的最终隐藏状态。

6. 具体输出值

output 的值
print("Output (所有时间步的隐藏状态):")
print(output)

输出示例:

tensor([[[ 0.1234,  0.5678,  0.9101,  0.1121,  0.3141],[ 0.4151,  0.6171,  0.8191,  0.0212,  0.2232]],[[ 0.4252,  0.6272,  0.8292,  0.0313,  0.2333],[ 0.4353,  0.6373,  0.8393,  0.0414,  0.2434]],[[ 0.4454,  0.6474,  0.8494,  0.0515,  0.2535],[ 0.4555,  0.6575,  0.8595,  0.0616,  0.2636]],[[ 0.4656,  0.6676,  0.8696,  0.0717,  0.2737],[ 0.4757,  0.6777,  0.8797,  0.0818,  0.2838]]],grad_fn=<StackBackward>)
hn 的值
print("hn (最后一个时间步的隐藏状态):")
print(hn)

输出示例:

tensor([[[ 0.4656,  0.6676,  0.8696,  0.0717,  0.2737],[ 0.4757,  0.6777,  0.8797,  0.0818,  0.2838]],[[ 0.4858,  0.6878,  0.8898,  0.0919,  0.2939],[ 0.4959,  0.6979,  0.8999,  0.1020,  0.3040]]],grad_fn=<StackBackward>)

batch_first=True时

以下是一个具体的例子,展示当 batch_first=True 时,PyTorch 中 torch.nn.RNN 的输入、输出以及参数的作用。


任务

假设有一个简单的任务:

  • 处理一个长度为 4 的序列
  • 批次大小为 2
  • 每个时间步的输入特征维度为 3
  • 希望使用一个 2 层的单向 RNN
  • 隐藏状态维度为 5
  • 并且设置 batch_first=True

2. 输入数据

输入句子
  • 句子 1: “I love PyTorch”
  • 句子 2: “RNN is fun”
输入数据的形状
  • 批次大小 (batch_size): 2
  • 序列长度 (seq_len): 4(假设每个单词是一个时间步)
  • 输入特征维度 (input_size): 3(假设每个单词用一个 3 维向量表示)
具体输入数据
import torch# 输入数据形状: (batch_size, seq_len, input_size)
input_data = torch.tensor([# 句子 1[[0.1, 0.2, 0.3],  # 第一个单词[0.7, 0.8, 0.9],  # 第二个单词[1.3, 1.4, 1.5],  # 第三个单词[1.9, 2.0, 2.1]], # 第四个单词# 句子 2[[0.4, 0.5, 0.6],  # 第一个单词[1.0, 1.1, 1.2],  # 第二个单词[1.6, 1.7, 1.8],  # 第三个单词[2.2, 2.3, 2.4]]  # 第四个单词
])
print("Input shape:", input_data.shape)  # 输出: torch.Size([2, 4, 3])

3. 初始隐藏状态

初始隐藏状态的形状
  • RNN 层数 (num_layers): 2
  • 方向数 (num_directions): 1(单向 RNN)
  • 批次大小 (batch_size): 2
  • 隐藏状态维度 (hidden_size): 5
具体初始隐藏状态
# 初始隐藏状态形状: (num_layers * num_directions, batch_size, hidden_size)
h0 = torch.zeros(2, 2, 5)  # 2层RNN,批次大小为2,隐藏状态维度为5
print("h0 shape:", h0.shape)  # 输出: torch.Size([2, 2, 5])

4. 定义 RNN 模型

import torch.nn as nn# 定义 RNN
rnn = nn.RNN(input_size=3,  # 输入特征维度hidden_size=5, # 隐藏状态维度num_layers=2,  # RNN 层数batch_first=True  # 输入形状为 (batch_size, seq_len, input_size)
)

5. 前向传播

计算输出
# 前向传播
output, hn = rnn(input_data, h0)print("Output shape:", output.shape)  # 输出: torch.Size([2, 4, 5])
print("hn shape:", hn.shape)          # 输出: torch.Size([2, 2, 5])
输出解析
  1. output:

    • 形状为 (batch_size, seq_len, hidden_size),即 (2, 4, 5)
    • 包含了每个时间步的隐藏状态。
    • 例如,output[0] 是第一个句子的所有时间步的隐藏状态,output[1] 是第二个句子的所有时间步的隐藏状态。
  2. hn:

    • 形状为 (num_layers, batch_size, hidden_size),即 (2, 2, 5)
    • 包含了最后一个时间步的隐藏状态。
    • 例如,hn[0] 是第一层的最终隐藏状态,hn[1] 是第二层的最终隐藏状态。

6. 具体输出值

output 的值
print("Output (所有时间步的隐藏状态):")
print(output)

输出示例:

tensor([[[ 0.1234,  0.5678,  0.9101,  0.1121,  0.3141],[ 0.4252,  0.6272,  0.8292,  0.0313,  0.2333],[ 0.4454,  0.6474,  0.8494,  0.0515,  0.2535],[ 0.4656,  0.6676,  0.8696,  0.0717,  0.2737]],[[ 0.4151,  0.6171,  0.8191,  0.0212,  0.2232],[ 0.4353,  0.6373,  0.8393,  0.0414,  0.2434],[ 0.4555,  0.6575,  0.8595,  0.0616,  0.2636],[ 0.4757,  0.6777,  0.8797,  0.0818,  0.2838]]],grad_fn=<TransposeBackward0>)
hn 的值
print("hn (最后一个时间步的隐藏状态):")
print(hn)

输出示例:

tensor([[[ 0.4656,  0.6676,  0.8696,  0.0717,  0.2737],[ 0.4757,  0.6777,  0.8797,  0.0818,  0.2838]],[[ 0.4858,  0.6878,  0.8898,  0.0919,  0.2939],[ 0.4959,  0.6979,  0.8999,  0.1020,  0.3040]]],grad_fn=<StackBackward>)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/4689.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构——AVL树的实现

Hello&#xff0c;大家好&#xff0c;这一篇博客我们来讲解一下数据结构中的AVL树这一部分的内容&#xff0c;AVL树属于是数据结构的一部分&#xff0c;顾名思义&#xff0c;AVL树是一棵特殊的搜索二叉树&#xff0c;我们接下来要讲的这篇博客是建立在了解搜索二叉树这个知识点…

【日志篇】(7.6) ❀ 01. 在macOS下刷新FortiAnalyzer固件 ❀ FortiAnalyzer 日志分析

【简介】FortiAnalyzer 是 Fortinet Security Fabric 安全架构的基础&#xff0c;提供集中日志记录和分析&#xff0c;以及端到端可见性。因此&#xff0c;分析师可以更有效地管理安全状态&#xff0c;将安全流程自动化&#xff0c;并快速响应威胁。具有分析和自动化功能的集成…

【KOA框架】koa框架基础及swagger接口文档搭建

koa是express的一层封装&#xff0c;语法比express更加简洁。所以有必要了解下koa的相关开发方法。 代码实现 package.json {"name": "koapp","version": "1.0.0","main": "index.js","scripts": {…

[深度学习]机器学习和深度学习

机器学习和深度学习 文章目录 机器学习和深度学习人工智能与机器学习和深度学习的关系侠义的机器学习深度学习的概念常见的神经网络的输入形式想要的输出(任务类别)深度学习的流程 线性函数与多层神经元 人工智能与机器学习和深度学习的关系 所谓人工智能就是&#xff0c;让计算…

【QT】已解决:Qt4.11.0无法使用MSVC编译器问题

目录 一、背景 1.本机环境 2.问题描述 3.问题解决前后对比图 二、详细操作 1.下载项目二所需qt环境 2.解决思路 3.安装VS2017 4.安装MSVC调试器 5.打开qtCreator查看编译器 5.编译运行项目二 三、参考 一、背景 1.本机环境 windows11 qtCreator4.11.0 minGW 64位…

C++ 模拟真人鼠标轨迹算法 - 防止游戏检测

一.简介 鼠标轨迹算法是一种模拟人类鼠标操作的程序&#xff0c;它能够模拟出自然而真实的鼠标移动路径。 鼠标轨迹算法的底层实现采用C/C语言&#xff0c;原因在于C/C提供了高性能的执行能力和直接访问操作系统底层资源的能力。 鼠标轨迹算法具有以下优势&#xff1a; 模拟…

从零开始:Spring Boot核心概念与架构解析

引言 在当今的Java开发领域&#xff0c;Spring Boot已经成为构建企业级应用的首选框架之一。它以其简洁、高效、易于上手的特点&#xff0c;极大地简化了Spring应用的开发过程。本文将从Spring Boot的核心概念入手&#xff0c;深入解析其架构设计和运行原理&#xff0c;帮助读…

【EdgeAI实战】(1)STM32 边缘 AI 生态系统

【EdgeAI实战】&#xff08;1&#xff09;STM32 边缘 AI 生态系统 【EdgeAI实战】&#xff08;1&#xff09;STM32 边缘 AI 生态系统 1. STM32 边缘人工智能1.1 X-CUBE-AI 扩展包1.2 STM32 AI Model Zoo1.3 ST AIoT Craft 2. STM32N6 AI 生态系统 (STM32N6-AI)2.1 STM32N6 AI 产…

SSE 实践:用 Vue 和 Spring Boot 实现实时数据传输

前言 大家好&#xff0c;我是雪荷。最近我在灵犀 BI 项目中引入了 SSE 技术&#xff0c;以保证图表的实时渲染&#xff0c;当图表渲染完毕服务端推送消息至浏览器端触发重新渲染。 什么是 SSE&#xff1f; SSE 全称为 Server-Send Events 意思是服务端推送事件。 SSE 相比于 …

关于机器学习的一份总结

在之前的文章中分别有详细的关于机器学习中某一学习算法的介绍&#xff0c;但缺少一个总体关于机器学习的总结&#xff0c;所以在这篇文中就是关于机器学习的一份总结。 在最近的日子中&#xff0c;人工智能日益火热起来&#xff0c;而机器学习是其中举足轻重的一部分&#xf…

浅谈计算机网络03 | 现代网络组成

现代网络组成 一 、网络生态体系1.1网络生态系统的多元主体1.2 网络接入设施的多样类型 二、现代网络的典型体系结构解析三、高速网络技术3.1 以太网技术3.2 Wi-Fi技术的深度剖析3.2.1 应用场景的多元覆盖3.2.2 标准升级与性能提升 3.3 4G/5G蜂窝网的技术演进3.3.1 蜂窝技术的代…

数据结构漫游记:队列的动态模拟实现(C语言)

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

Ubuntu22.04安装paddle GPU版本

文章目录 确立版本安装CUDA与CUDNN安装paddle 确立版本 查看官网信息&#xff0c;确立服务版本&#xff1a;https://www.paddlepaddle.org.cn/documentation/docs/zh/2.6/install/pip/linux-pip.html 安装CUDA与CUDNN 通过nvidia-smi查看当前显卡驱动版本&#xff1a; 通过…

2024年度总结:从后端Java到全栈成长的蜕变

目录 前言1. 用数据与实践书写成长篇章2. 技术与生活的双重蜕变3. 技术的进阶与生活的绽放 前言 今年是我入行的第十年&#xff0c;也是记录在CSDN平台上的第五年。这五年来&#xff0c;我始终坚持记录成长的点滴&#xff0c;将个人事业与博客创作紧密相连。一路走来&#xff0…

麦田物语学习笔记:创建TransitionManager控制人物场景切换

基本流程 制作场景之间的切换 1.代码思路 (1)为了实现不同场景切换,并且保持当前的persistentScene一直存在,则需要一个Manager去控制场景的加载和卸载,并且在加载每一个场景之后,都要将当前的场景Set Active Scene,保证其为激活的场景,在卸载的时候也可以方便调用当前激活的场…

无人机高速无刷动力电机核心设计技术

一、技术概述 无刷电机优势&#xff1a; 高效率&#xff1a;无刷电机由于去除了电刷和换向器&#xff0c;减少了能量损失&#xff0c;因此具有更高的效率。 长寿命&#xff1a;电刷和换向器的磨损是导致传统有刷电机寿命较短的主要原因&#xff0c;而无刷电机则避免了这一问…

Linux C\C++方式下的文件I/O编程

【图书推荐】《Linux C与C一线开发实践&#xff08;第2版&#xff09;》_linux c与c一线开发实践pdf-CSDN博客 《Linux C与C一线开发实践&#xff08;第2版&#xff09;&#xff08;Linux技术丛书&#xff09;》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 Lin…

python编程-OpenCV(图像读写-图像处理-图像滤波-角点检测-边缘检测)角点检测

角点检测&#xff08;Corner Detection&#xff09;是计算机视觉和图像处理中重要的步骤&#xff0c;主要用于提取图像中的关键特征&#xff0c;以便进行后续的任务&#xff0c;比如图像匹配、物体识别、运动跟踪等。下面介绍几种常用的角点检测方法及其应用。 1. Harris角点检…

QT开发-T113 Linux 主板QC配置套件

此篇文章用于记录在Linux主板上使用QT开发项目的套件配置步骤 进入QC软件&#xff0c;点击 Manage Kits… 选择项目对应的QT Version : 一般有一个项目对应的qmake 文件&#xff0c;选择导入即可 如果首次导入提示 qmake could not be added 需要先对项目进行命令行编译(具体命…

【云岚到家】-day03-门户缓存实现实战

【云岚到家】-day03-门户缓存实现实战 1.定时任务更新缓存 1.1 搭建XXL-JOB环境 1.1.1 分布式调度平台XXL-JOB介绍 对于开通区域列表的缓存数据需要由定时任务每天凌晨更新缓存&#xff0c;如何实现定时任务呢&#xff1f; 1.使用jdk提供的Timer定时器 示例代码如下&#xf…