现代循环神经网络(GRU、LSTM)(Pytorch 14)

一  简介

前一章中我们介绍了循环神经网络的基础知识,这种网络 可以更好地处理序列数据。我们在文本数据上实现 了基于循环神经网络的语言模型,但是对于当今各种各样的序列学习问题,这些技术可能并不够用。

例如,循环神经网络在实践中一个常见问题是 数值不稳定性。尽管我们已经应用了梯度裁剪等技巧来缓解这 个问题,但是仍需要通过设计更复杂的序列模型来进一步处理它。具体来说,我们将引入两个广泛使用的网络,即 门控循环单元(gated recurrent units,GRU)和 长短期记忆网络(long short‐term memory,LSTM)。 然后,我们将基于一个单向隐藏层来扩展循环神经网络架构。我们将描述具有多个隐藏层的深层架构,并讨论基于前向和后向循环计算的双向设计。现代循环网络经常采用这种扩展。

事实上,语言建模只揭示了序列学习能力的冰山一角。在各种序列学习问题中,如自动语音识别、文本到语音转换和机器翻译,输入和输出都是任意长度的序列。为了阐述如何拟合这种类型的数据,我们将以机器翻译为例介绍基于循环神经网络的“编码器-解码器”架构和束搜索,并用它们来生成序列。

二  门控循环单元(GRU)

我们讨论了如何在循环神经网络中计算梯度,以及矩阵连续乘积可以导致梯度消失或梯度爆炸 的问题。下面我们简单思考一下这种梯度异常在实践中的意义:

  • 我们可能会遇到这样的情况:早期观测值对预测所有未来观测值具有非常重要的意义。考虑一个极端 情况,其中第一个观测值包含一个校验和,目标是在序列的末尾辨别校验和是否正确。在这种情况下, 第一个词元的影响至关重要。我们希望有某些机制能够在一个记忆元里存储重要的早期信息。如果没有这样的机制,我们将不得不给这个观测值指定一个非常大的梯度,因为它会影响所有后续的观测值。
  • 我们可能会遇到这样的情况:一些词元没有相关的观测值。例如,在对网页内容进行情感分析时,可能有一些辅助HTML代码与网页传达的情绪无关。我们希望有一些机制来跳过隐状态表示中的此类词元
  • 我们可能会遇到这样的情况:序列的各个部分之间存在逻辑中断。例如,书的章节之间可能会有过渡存 在,或者证券的熊市和牛市之间可能会有过渡存在。在这种情况下,最好有一种方法来重置我们的内部 状态表示。

在学术界已经提出了许多方法来解决这类问题。其中最早的方法是“长短期记忆”(long‐short‐term memory, LSTM)(Hochreiter and Schmidhuber, 1997)。门控循环单元(gated recurrent unit, GRU)(Cho et al., 2014) 是一个稍微简化的变体,通常能够提供同等的效果,并且计算的速度明显更快。由于门控循环单元更简单,我们从它开始解读。

2.1 门控隐状态

门控循环单元与普通的循环神经网络之间的关键区别在于:前者支持隐状态的门控。这意味着模型有专门的机制来确定应该何时更新隐状态,以及应该何时重置隐状态。这些机制是可学习的,并且能够解决了上面列出的问题。例如,如果第一个词元非常重要,模型将学会在第一次观测之后不更新隐状态。同样,模型也可以学会跳过不相关的临时观测。最后,模型还将学会在需要的时候重置隐状态。下面我们将详细讨论各类门控。

我们首先介绍 重置门(reset gate)和 更新门(update gate)。我们把它们设计成(0, 1)区间中的向量,这样我 们就可以进行凸组合。重置门允许我们控制“可能还想记住”的过去状态的数量更新门将允许我们控制新状态中有多少个是旧状态的副本。 我们从构造这些门控开始。下图描述了门控循环单元中的重置门和更新门的输入,输入是由当前时间步的输入和前一时间步的隐状态给出。两个门的输出是由使用sigmoid激活函数的两个全连接层给出

总之,门控循环单元 具有以下两个显著特征

  • 重置门有助于捕获序列中的 短期依赖 关系;
  • 更新门有助于捕获序列中的 长期依赖 关系。

2.2 从零开始实现

为了更好地理解门控循环单元模型,我们从零开始实现它。使用的时间机器数据集:

import torch
from torch import nn
from d2l import torch as d2l

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

下一步是 初始化模型参数。我们从标准差为0.01的高斯分布中提取权重,并将偏置项设为0,超参 数num_hiddens定义 隐藏单元的数量实例化与更新门重置门候选隐状态 输出层相关的所有权重和偏置

def get_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size
    
    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01

    def three():
        return (normal((num_inputs, num_hiddens)),
                 normal((num_hiddens, num_hiddens)),
                 torch.zeros(num_hiddens, device=device))
    
    W_xz, W_hz, b_z = three()
    W_xr, W_hr, b_r = three()
    W_xh, W_hh, b_h = three()
    
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)
    
    params = [W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

定义模型,现在我们将定义隐状态的初始化函数init_gru_state。定义的nit_rnn_state函数,此函数 返回一个形状为(批量大小,隐藏单元个数)的张量,张量的值全部为零。现在我们准备定义门控循环单元模型,模型的架构与基本的循环神经网络单元是相同的,只是权重更新公式 更为复杂。

def init_gru_state(batch_size, num_hiddens, device):
    return (torch.zeros((batch_size, num_hiddens), device=device),)

def gru(inputs, state, params):
    W_xz, W_hz, b_z, W_xr, W_hr, b_r, W_xh, W_hh, b_h, W_hq, b_q = params
    H, = state
    outputs = []
    for X in inputs:
        Z = torch.sigmoid((X @ W_xz) + (H @ W_hz) + b_z)
        R = torch.sigmoid((X @ W_xr) + (H @ W_hr) + b_r)
        H_tilda = torch.tanh((X @ W_xh) + ((R * H) @ W_hh) + b_h)
        H = Z * H + (1 - Z) * H_tilda
        Y = H @ W_hq + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H,)

训练与预测,训练结束后,我们分别打印输出训练集的困惑度,以及前缀“time traveler”和“traveler”的预测序列上的困惑度。

vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_params,
                            init_gru_state, gru)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

2.3 简洁实现

高级API包含了前文介绍的所有配置细节,所以我们 可以直接实例化门控循环单元模型。这段代码的运行速度要快得多,因为它使用的是编译好的运算符而不是Python来处理之前阐述的许多细节。

num_inputs = vocab_size
gru_layer = nn.GRU(num_inputs, num_hiddens)
model = d2l.RNNModel(gru_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

小结:

  • 门控循环神经网络可以更好地 捕获时间步距离很长的序列上的依赖关系
  • 重置门 有助于捕获序列中的 短期 依赖关系。
  • 更新门 有助于捕获序列中的 长期 依赖关系。
  • 重置门打开时,门控循环单元包含基本循环神经网络;更新门打开时,门控循环单元可以跳过子序列

三 长短期记忆网络(LSTM)

长期以来,隐变量模型存在着长期信息保存和短期输入缺失的问题。解决这一问题的最早方法之一是长短期 存储器(long short‐term memory,LSTM)(Hochreiter and Schmidhuber, 1997)。它有许多与门控循环单元一样的属性。有趣的是,长短期记忆网络的设计比门控循环单元稍微复杂一些,却比门控循环单 元早诞生了近20年。

3.1 门控记忆元

可以说,长短期记忆网络的设计灵感 来自于计算机的逻辑门。长短期记忆网络引入了记忆元(memory cell), 或简称为单元(cell)。有些文献认为记忆元是隐状态的一种特殊类型,它们与隐状态具有相同的形状,其设计目的是用于记录附加的信息。为了控制记忆元,我们需要许多门。其中一个门用来从单元中输出条目,我 们将其称为 输出门(output gate)。另外一个门用来决定何时将数据读入单元,我们将其称为 输入门(input gate)。我们还需要一种机制来重置单元的内容,由遗忘门(forget gate)来管理,这种设计的动机与门控循 环单元相同,能够通过专用机制决定什么时候记忆或忽略隐状态中的输入。让我们看看这在实践中是如何运 作的。

3.2 从零开始实现

现在,我们从零开始实现长短期记忆网络。我们首先 加载时光机器数据集

import torch
from torch import nn
from d2l import torch as d2l

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

初始化模型参数,我们需要 定义和初始化模型参数。如前所述,超参数num_hiddens定义隐藏单元的数量。我们按照标准差0.01的高斯分布初始化权重,并将偏置项设为0。

def get_lstm_params(vocab_size, num_hiddens, device):
    num_inputs = num_outputs = vocab_size
    
    def normal(shape):
        return torch.randn(size=shape, device=device) * 0.01
    
    def three():
        return (normal((num_inputs, num_hiddens)),
                 normal((num_hiddens, num_hiddens)),
                 torch.zeros(num_hiddens, device=device))
    
    W_xi, W_hi, b_i = three()
    W_xf, W_hf, b_f = three()
    W_xo, W_ho, b_o = three()
    W_xc, W_hc, b_c = three()
    
    W_hq = normal((num_hiddens, num_outputs))
    b_q = torch.zeros(num_outputs, device=device)
    
    params = [W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc,
              b_c, W_hq, b_q]
    for param in params:
        param.requires_grad_(True)
    return params

定义模型,在初始化函数中,长短期记忆网络的 隐状态需要返回一个额外的记忆元,单元的值为0,形状为(批量大小, 隐藏单元数)。因此,我们得到以下的状态初始化。实际模型的定义与我们前面讨论的一样:提供三个门和一个额外的记忆元。请注意,只有隐状态才会传递到输出层,而记忆元Ct不直接参与输出计算

def init_lstm_state(batch_size, num_hiddens, device):
    return (torch.zeros((batch_size, num_hiddens), device=device),
             torch.zeros((batch_size, num_hiddens), device=device))

def lstm(inputs, state, params):
    [W_xi, W_hi, b_i, W_xf, W_hf, b_f, W_xo, W_ho, b_o, W_xc, W_hc, 
     b_c, W_hq, b_q] = params
    (H, C) = state
    outputs = []
    for X in inputs:
        I = torch.sigmoid((X @ W_xi) + (H @ W_hi) + b_i)
        F = torch.sigmoid((X @ W_xf) + (H @ W_hf) + b_f)
        O = torch.sigmoid((X @ W_xo) + (H @ W_ho) + b_o)
        C_tilda = torch.tanh((X @ W_xc) + (H @ W_hc) + b_c)
        C = F * C + I * C_tilda
        H = O * torch.tanh(C)
        Y = (H @ W_hq) + b_q
        outputs.append(Y)
    return torch.cat(outputs, dim=0), (H, C)

训练和预测,让我们通过实例化 引入的RNNModelScratch类来训练一个长短期记忆网络。

vocab_size, num_hiddens, device = len(vocab), 256, d2l.try_gpu()
num_epochs, lr = 500, 1
model = d2l.RNNModelScratch(len(vocab), num_hiddens, device, get_lstm_params,
                            init_lstm_state, lstm)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

3.3 简洁实现

使用高级API,我们可以 直接实例化LSTM模型。高级API封装了前文介绍的所有配置细节。这段代码的运行速度要快得多,因为它使用的是编译好的运算符。

num_inputs = vocab_size
lstm_layer = nn.LSTM(num_inputs, num_hiddens)
model = d2l.RNNModel(lstm_layer, len(vocab))
model = model.to(device)
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

长短期记忆网络是典型的具有重要状态控制的隐变量自回归模型。多年来已经提出了其许多变体,例如,多层残差连接不同类型的正则化。然而,由于序列的长距离依赖性,训练长短期记忆网络和其他序列模型 (例如门控循环单元)的 成本是相当高 的。在后面的内容中,我们将讲述更高级的替代模型,如Transformer

小结:

  • 长短期记忆网络有三种类型的门:输入门遗忘门 和 输出门
  • 长短期记忆网络的隐藏层输出包括“隐状态”和“记忆元”。只有 隐状态会传递到输出层,而 记忆元完全属于内部信息
  • 长短期记忆网络可以 缓解梯度消失和梯度爆炸

四 深度循环神经网络

到目前为止,我们 只讨论了具有一个单向隐藏层的循环神经网络。其中,隐变量和观测值与具体的函数形式的交互方式是相当随意的。只要交互类型建模具有足够的灵活性,这就不是一个大问题。然而,对一个单层来说,这可能具有相当的挑战性。之前在线性模型中,我们通过添加更多的层来解决这个问题。而 在循环神经网络中,我们首先需要确定如何添加更多的层,以及在哪里添加额外的非线性,因此这个问题有点棘手。

事实上,我们可以 将多层循环神经网络堆叠在一起,通过对几个简单层的组合,产生了一个灵活的机制。特别是,数据可能与不同层的堆叠有关。例如,我们可能希望保持有关金融市场状况(熊市或牛市)的宏观数 据可用,而微观数据只记录较短期的时间动态

下图描述了一个具有 L个隐藏层的深度循环神经网络每个隐状态都连续地传递到当前层的下一个时间步 和下一层的当前时间步

4.1 简洁实现

实现多层循环神经网络所需的许多逻辑细节在高级API中都是现成的。简单起见,我们仅示范使用此类内置 函数的实现方式。以长短期记忆网络模型为例,实际上唯一 的区别是我们 指定了层的数量,而不是使用单一层这个默认值。像往常一样,我们从 加载数据集 开始。

import torch
from torch import nn
from d2l import torch as d2l

batch_size, num_steps = 32, 35
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)

像选择超参数这类架构决策和基础模块的决策非常相似。因为我们有不同的词元,所以输入和输出都选择 相同数量,即vocab_size隐藏单元的数量仍然是256。唯一的区别是,我们现在 通过num_layers的值来设定 隐藏层数

vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
num_inputs = vocab_size
device = d2l.try_gpu()
lstm_layer = nn.LSTM(num_inputs, num_hiddens, num_layers)
model = d2l.RNNModel(lstm_layer, len(vocab))
model = model.to(device)

训练与预测,由于使用了长短期记忆网络模型来实例化两个层,因此训练速度被大大降低了。

num_epochs, lr = 500, 2
d2l.train_ch8(model, train_iter, vocab, lr * 1.0, num_epochs, device)

小结:

  • 在深度循环神经网络中,隐状态的信息被传递到当前层的下一时间步和下一层的当前时间步
  • 许多不同风格的深度循环神经网络,如 长短期记忆网络门控循环单元、或 经典循环神经网络。这些模型在深度学习框架的高级API中都有涵盖。
  • 总体而言,深度循环神经网络 需要大量的调参(如学习率和修剪)来确保合适的收敛,模型的初始化也需要谨慎。

五 双向循环神经网络

在序列学习中,我们以往假设的目标是:在给定观测的情况下(例如,在时间序列的上下文中或在语言模型 的上下文中),对下一个输出进行建模

5.1 双向循环神经网络的错误应用

由于 双向循环神经网络使用了过去的和未来的数据所以我们不能盲目地将这一语言模型应用于任何预测任务。尽管模型产出的困惑度是合理的,该模型预测未来词元的能力却可能存在严重缺陷。我们用下面的示例 代码引以为戒,以防在 错误的环境 中使用它们。

import torch
from torch import nn
from d2l import torch as d2l
# 加载数据
batch_size, num_steps, device = 32, 35, d2l.try_gpu()
train_iter, vocab = d2l.load_data_time_machine(batch_size, num_steps)
# 通过设置“bidirective=True”来定义双向LSTM模型
vocab_size, num_hiddens, num_layers = len(vocab), 256, 2
num_inputs = vocab_size
lstm_layer = nn.LSTM(num_inputs, num_hiddens, num_layers, bidirectional=True)
model = d2l.RNNModel(lstm_layer, len(vocab))
model = model.to(device)
# 训练模型
num_epochs, lr = 500, 1
d2l.train_ch8(model, train_iter, vocab, lr, num_epochs, device)

小结:

  • 在双向循环神经网络中,每个时间步的隐状态由当前时间步的前后数据同时决定
  • 双向循环神经网络与概率图模型中的“前向‐后向”算法具有相似性。
  • 双向循环神经网络主要用于 序列编码 给定双向上下文的观测估计
  • 由于梯度链更长,因此双向循环神经网络的 训练代价非常高

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java中接口的默认方法

为什么要使用默认方法 当我们把一个程序的接口写完后 用其他的类去实现,此时如果程序需要再添加一个抽象方法的时候我们只有两种选择 将抽象方法写在原本的接口中 但是这样写会导致其他所有改接口的实现类都需要实现这个抽象方法比较麻烦 写另一个接口 让需要的实…

23.哀家要长脑子了!

目录 1.290. 单词规律 - 力扣(LeetCode) 2.532. 数组中的 k-diff 数对 - 力扣(LeetCode) 3.205. 同构字符串 - 力扣(LeetCode) 4.138. 随机链表的复制 - 力扣(LeetCode) 5.599. 两…

Spring Boot与OpenCV:融合机器学习的智能图像与视频处理平台

🧑 作者简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

VMware虚拟机中ubuntu使用记录(6)—— 如何标定单目相机的内参(张正友标定法)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、张正友相机标定法1. 工具的准备2. 标定的步骤(1) 启动相机(2) 启动标定程序(3) 标定过程的操作(5)可能的报错 3. 标定文件内容解析 前言 张正友相机标定法…

什么是PWM?

1.PWM也叫做脉冲宽度调制,它是一种模拟控制方式,根据相应 载荷 的变化来调制晶体管基级和MOS管栅极的偏置,来实现 晶体管 或 MOS管 导通时间的改变,从而实现开关稳压电源输出的改变。 这种方式能使电源的输出电压在工作条件变化时…

linux的基础入门(2)

环境变量 在Shell中,正确的赋值语法是没有空格的,即变量名数值。所以,正确的方式是: tmpshy 这样就将变量tmp赋值为"shy"了。 注意:并不是任何形式的变量名都是可用的,变量名只能是英文字母、…

Reac19 升级指南

Reactv19 已经发布 beta 版本,想要快速体验如何升级到 v19 版本尝鲜的朋友们可以查阅进行了解 前言 React 已于近日发布了 v19 的 beta 版本,同时为了帮助后续的 v19 升级,也同时发布了 v18.3.0的正式版, 与 v18.2 版本完全相同…

JavaEE初阶Day 15:文件IO(1)

目录 Day 15:文件IO(1)IO文件1. 路径2. 文件的分类3. 使用Java针对文件系统进行操作3.1 属性3.2 构造方法3.3 方法 Day 15:文件IO(1) IO I:Input输入 O:Output输出 输入输出规则…

【golang-ent】go-zero框架 整合 ent orm框架实现一对一 一对多 多种姿势查询方式

一、ent的 O2O 问题 官方文档如下: https://entgo.io/zh/docs/schema-edges#o2o-same-type 1、ent O2O问题 官方提供了三种 one2one的方式,可以看到他全部使用了 mysql的 foregionKey 的方式进行关联,虽然举例了单表和双表的不同使用方式,但是我们实际使用mysql中是不创建…

深度解析 Spring 源码:探寻Bean的生命周期

文章目录 一、 Bean生命周期概述二、Bean生命周期流程图三、Bean生命周期验证3.1 代码案例3.2 执行结果 四、Bean生命周期源码4.1 setBeanName()4.2 setBeanFactory()4.3 setApplicationContext()4.4 postProcessBeforeInitialization()4.5 afterPropertiesSet()4.6 postProces…

银行数字化转型导师坚鹏:银行数字化转型5大战略定位

银行数字化转型必须建立清晰的战略定位,可以从以下5个方面建立,使广大银行员工明白银行数字化转型的重要性。 1.国家战略落地的重要抓手 银行是中国金融体系的核心,银行是做好“科技金融、绿色金融、普惠金融、养老金融和数字金融‘五篇大文…

【c++】继承学习(二):探索 C++ 中派生类的默认机制与静态成员共享

🔥个人主页:Quitecoder 🔥专栏:c笔记仓 目录 1.派生类的默认成员函数2.继承与友元3.继承与静态成员 朋友们大家好,本篇文章我们来学习继承的第二部分 1.派生类的默认成员函数 来看下面的类: class Person…

【自留】运行一个开源项目

运行一个开源项目 首先是运行起来 1. 拿到地址 拿到你想要的项目的地址 2. 克隆 打开编辑器 VSCode ,创建一个放项目的文件夹,控制台输入以下代码克隆项目 git clone 克隆地址gitee克隆地址在这看: github上项目的话,在这…

【python的魅力】:教你如何用几行代码实现文本语音识别

文章目录 引言一、运行效果二、文本转换为语音2.1 使用pyttsx32.2 使用SAPI实现文本转换语音2.3 使用 SpeechLib实现文本转换语音 三、语音转换为文本3.1 使用 PocketSphinx实现语音转换文本 引言 语音识别技术,也被称为自动语音识别,目标是以电脑自动将…

SSM+Vue+小程序+基于微信小程序的高校学生事务管理系统

项目配合学校日常生活通知,考试等管理需要,开发学校事务管理系统,maven管理依赖,mybatis处理数据库交互 学校管理 学院管理 班级管理 年级管理 教师管理 通知公告管理 学生资料管理 待办事项管理 教务处通知管理 讲座通…

【Conda】解决无名虚拟环境问题

文章目录 问题描述:无名虚拟环境解决步骤1 添加虚拟环境目录到envs步骤2 成功命名 问题描述:无名虚拟环境 如果不指定创建目录,默认创建在C盘用户目录下,这应该是很多人不愿意的吧。 指定目录创建虚拟环境命令如下: …

vector的使用

1.构造函数 void test_vector1() {vector<int> v; //无参的构造函数vector<int> v2(10, 0);//n个value构造&#xff0c;初始化为10个0vector<int> v3(v2.begin(), v2.end());//迭代器区间初始化,可以用其他容器的区间初始化vector<int> v4(v3); //拷贝…

基于yolov8的苹果腐败检测系统,系统既支持图像检测,也支持视频和摄像实时检测(pytorch框架)【python源码+UI界面+功能源码详解】

更多目标检测和图像分类识别项目可看我主页其他文章 功能演示&#xff1a; 基于yolov8的苹果腐败检测系统&#xff0c;系统既支持图像检测&#xff0c;也支持视频和摄像实时检测_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov8的苹果腐败检测系统是在pytorc…

STM32 F103C8T6学习笔记17:类IIC通信(SMBus协议)—MLX90614红外非接触温度计

今日学习配置MLX90614红外非接触温度计 与 STM32 F103C8T6 单片机的通信 文章提供测试代码讲解、完整工程下载、测试效果图 本文需要用到的大概基础知识&#xff1a;1.3寸OLED配置通信显示、IIC通信、 定时器配置使用 这里就只贴出我的 OLED驱动方面的网址链接了&#xff1a…

Golang数组与切片

文章目录 数组数组介绍数组的定义方式访问与修改数组元素遍历数组元素数组指针 切片切片介绍切片的定义方式访问与修改切片元素添加切片元素切片的拷贝遍历切片元素string的切片 数组 数组介绍 数组介绍 在Go中&#xff0c;数组是一个由固定长度的特定类型元素组成的序列&…