循环神经网络模块介绍(Pytorch 12)

news2024/11/20 0:26:00

到目前为止,我们遇到过两种类型的数据:表格数据和图像数据。对于图像数据,我们设计了专门的卷积神经网络架构(cnn)来为这类特殊的数据结构建模。换句话说,如果我们拥有一张图像,我们 需要有效地利用其像素位置,假若我们对图像中的像素位置进行重排,就会对图像中内容的推断造成极大的困难。

最重要的是,到目前为止我们默认数据都来自于某种分布,并且 所有样本都是独立同分布 的(independently and identically distributed)。然而,大多数的数据并非如此。例如,文章中的单词是按顺序写的,如 果顺序被随机地重排,就很难理解文章原始的意思。同样,视频中的图像帧、对话中的音频信号以及网站上 的浏览行为都是有顺序的。因此,针对此类数据而设计特定模型,可能效果会更好。 另一个问题来自这样一个事实:我们不仅仅可以接收一个序列作为输入,而是还可能期望继续猜测这个序列的后续。例如,一个任务可以是继续预测2, 4, 6, 8, 10, . . .。这在时间序列分析中是相当常见的,可以用来预测 股市的波动、患者的体温曲线或者赛车所需的加速度。同理,我们需要能够处理这些数据的特定模型。

简言之,如果说 卷积神经网络可以有效地处理空间信息,那么本章的循环神经网络(recurrent neural network, RNN)则可以更好地处理序列信息。循环神经网络通过引入状态变量存储过去的信息和当前的输入,从而可 以确定当前的输出。 许多使用循环网络的例子都是基于文本数据的,因此我们将在本章中重点介绍语言模型。在对序列数据进行 更详细的回顾之后,我们将介绍文本预处理的实用技术。然后,我们将讨论语言模型的基本概念,并将此讨论作为循环神经网络设计的灵感。

一 序列模型

处理序列数据需要统计工具和新的深度神经网络架构。

1.1 自回归模型

以 预测股票价格为例。

为了实现这个预测,交易员可以使用回归模型。仅有一个主要问题:输入数据的 数量,输入xt−1, . . . , x1本身因t而异。也就是说,输入数据的数量这个数字将会随着我们遇到的数据量的增加 而增加,因此需要一个近似方法来使这个计算变得容易处理。本章后面的大部分内容将围绕着如何有效估计 P(xt | xt−1, . . . , x1)展开。简单地说,它归结为以下两种策略。

第一种策略,假设在现实情况下相当长的序列 xt−1, . . . , x1可能是不必要的,因此我们只需要满足某个长度 为τ的时间跨度,即使用观测序列xt−1, . . . , xt−τ。当下获得的最直接的好处就是参数的数量总是不变的,至少 在t > τ时如此,这就使我们能够训练一个上面提及的深度网络。这种模型被称为自回归模型(autoregressive models),因为它们是对自己执行回归。

第二种策略,如 下图所示,是保留一些对过去观测的总结ht,并且同时更新预测xˆt和总结ht。这就产生了 基于xˆt = P(xt | ht)估计xt,以及公式ht = g(ht−1, xt−1)更新的模型。由于ht从未被观测到,这类模型也被称 为 隐变量自回归模型(latent autoregressive models)。

这两种情况都有一个显而易见的问题:如何生成训练数据?一个经典方法是使用历史观测来预测下一个未来观测。显然,我们并不指望时间会停滞不前。然而,一个常见的假设是虽然特定值xt可能会改变,但是序列 本身的动力学不会改变。这样的假设是合理的,因为新的动力学一定受新的数据影响,而我们不可能用目前 所掌握的数据来预测新的动力学。统计学家称 不变的动力学为静止的(stationary)。

注意,如果我们处理的是离散的对象(如单词),而不是连续的数字,则上述的考虑仍然有效。唯一的差别是, 对于离散的对象,我们需要使用分类器而不是回归模型来估计P(xt | xt−1, . . . , x1)。

1.2 马尔可夫模型

回想一下,在自回归模型的近似法中,我们使用xt−1, . . . , xt−τ 而不是xt−1, . . . , x1来估计xt。只要这种是近似精确的,我们就说序列满足马尔可夫条件(Markov condition)。

当假设xt仅是离散值时,这样的模型特别棒,因为在这种情况下,使用动态规划可以沿着马尔可夫链精确地 计算结果

利用这一事实,我们只需要考虑过去观察中的一个非常短的历史:P(xt+1 | xt, xt−1) = P(xt+1 | xt)。隐马尔 可夫模型中的动态规划超出了本节的范围,而动态规划这些计算工具已经在控制算法和强化学习算法广泛使用。

1.3 模型训练

在了解了上述统计工具后,让我们在实践中尝试一下!首先,我们生成一些数据:使用 正弦函数和一些可加 性噪声来生成序列数据,时间步为1, 2, . . . , 1000。

%matplotlib inline
import torch
from torch import nn
from d2l import torch as d2l

T = 1000
time = torch.arange(1, T + 1, dtype=torch.float32)
x = torch.sin(0.01 * time) + torch.normal(0, 0.2, (T,))
d2l.plot(time, [x], 'time', 'x', xlim=[1, 1000], figsize=(6, 3))

接下来,我们将这个序列转换为 模型的特征-标签(feature‐label)对。基于嵌入维度τ,我们将数据映射为 数据对yt = xt 和xt = [xt−τ , . . . , xt−1]。这比我们提供的数据样本少了τ个,因为我们没有足够的历史记录来 描述前τ个数据样本。一个简单的解决办法是:如果拥有足够长的序列就丢弃这几项;另一个方法是用零填 充序列。在这里,我们 仅使用前600个“特征-标签”对 进行 训练

tau = 4
features = torch.zeros((T - tau, tau))
for i in range(tau):
    features[:, i] = x[i: T - tau + i]
labels = x[tau:].reshape((-1, 1))
batch_size, n_train = 16, 600
train_iter = d2l.load_array((features[:n_train], labels[:n_train]),
                           batch_size, is_train=True)

def init_weights(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
        
def get_net():
    net = nn.Sequential(nn.Linear(4, 10), 
                        nn.ReLU(),
                        nn.Linear(10, 1))
    net.apply(init_weights)
    return net

loss = nn.MSELoss(reduction='none')

def train(net, train_iter, loss, epochs, lr):
    trainer = torch.optim.Adam(net.parameters(), lr)
    for epoch in range(epochs):
        for X, y in train_iter:
            trainer.zero_grad()
            l = loss(net(X), y)
            l.sum().backward()
            trainer.step()
        print(f'epoch:{epoch + 1}, loss:{d2l.evaluate_loss(net, train_iter, loss): f}')
        
net = get_net()
train(net, train_iter, loss, 5, 0.01)

1.4 执行预测

onestep_preds = net(features)
d2l.plot([time, time[tau:]], 
         [x.detach().numpy(), onestep_preds.detach().numpy()], 'time',
         'x', legend=['data', '1-step preds'], 
         xlim = [1, 1000], figsize=(6, 3))

正如我们所料,单步预测效果不错。即使这些预测的时间步超过了600 + 4(n_train + tau),其结果看起来 仍然是可信的。然而有一个小问题:如果数据观察序列的时间步只到604,我们需要一步一步地向前迈进

通常,对于直到xt的观测序列,其在时间步t + k处的预测输出xˆt+k 称为k步预测(k‐step‐ahead‐prediction)。 由于我们的观察已经到了x604,它的k步预测是xˆ604+k。换句话说,我们必须使用我们自己的预测(而不是原 始数据)来进行多步预测。

multistep_preds = torch.zeros(T)
multistep_preds[: n_train + tau] = x[: n_train + tau]
for i in range(n_train + tau, T):
    multistep_preds[i] = net(multistep_preds[i - tau: i].reshape((1, -1)))

d2l.plot([time, time[tau:], time[n_train + tau:]],
         [x.detach().numpy(), onestep_preds.detach().numpy(),
          multistep_preds[n_train + tau:].detach().numpy()], 'time',
         'x', legend=['data', '1-step preds', 'multistep preds'], 
         xlim=[1, 1000], figsize=(6, 3))

如上面的例子所示,绿线的预测显然并不理想。经过几个预测步骤之后,预测的结果很快就会衰减到一个常数。为什么这个算法效果这么差呢?事实是由于错误的累积:假设在步骤1之后,我们积累了一些错误ϵ1 = ¯ϵ。 于是,步骤2的输入被扰动了ϵ1,结果积累的误差是依照次序的ϵ2 = ¯ϵ + cϵ1,其中c为某个常数,后面的预测 误差依此类推。因此误差可能会相当快地偏离真实的观测结果。例如,未来24小时的天气预报往往相当准确, 但超过这一点,精度就会迅速下降

基于k = 1, 4, 16, 64,通过对整个序列预测的计算,让我们更仔细地看一下k步预测的困难。

## 步长使用64做预测
max_steps = 64
features = torch.zeros((T - tau - max_steps + 1, tau + max_steps))
for i in range(tau):
    features[:, i] = x[i: i + T - tau - max_steps + 1]

for i in range(tau, tau + max_steps):
    features[:, i] = net(features[:, i - tau: i]).reshape(-1)

steps = (1, 4, 16, 64)
d2l.plot([time[tau + i - 1: T - max_steps + i] for i in steps],
         [features[:, (tau + i - 1)].detach().numpy() for i in steps], 'time', 'x',
         legend=[f'{i}-step preds' for i in steps], xlim=[5, 1000], 
         figsize=(6, 3))

以上例子清楚地说明了当我们试图预测更远的未来时,预测的质量是如何变化的。虽然“4步预测”看起来仍然不错,但超过这个跨度的任何预测几乎都是无用的

小结:

  • 内插法(在现有观测值之间进行估计)和 外推法(对超出已知观测范围进行预测)在实践的难度上差别很大。因此,对于所拥有的序列数据,在训练时始终要尊重其时间顺序,即最好不要基于未来的数据进 行训练
  • 序列模型的估计需要专门的统计工具,两种较流行的选择是 自回归模型和隐变量自回归模型
  • 对于时间是向前推进的因果模型,正向估计通常比反向估计更容易。
  • 对于直到时间步t的观测序列,其在时间步t + k的预测输出是“k步预测”。随着我们对预测时间k值的 增加,会造成误差的快速累积和预测质量的极速下降。

二 文本预处理

对于 序列数据处理 问题,这样的数据存在许多种形式,文本是最常见例子之一。例如,一篇文章可以被简单地看作一串单词序列,甚至是一串字符序列。我们将解析文本的常见预处理步骤。这些步骤通常包括:

  1. 文本作为字符串加载到内存中。
  2. 将字符串拆分为词元(如单词和字符)。
  3. 建立一个词表,将拆分的词元映射到数字索引。
  4. 将文本转换为数字索引序列,方便模型操作

2.1 读取数据集

首先,我们从H.G.Well的 时光机器 中加载文本。这是一个相当小的语料库,只有 30000多个单词,但足够我 们小试牛刀,而现实中的文档集合可能会包含数十亿个单词。下面的函数将数据集读取到由多条文本行组成 的列表中,其中每条文本行都是一个字符串。为简单起见,我们在这里忽略了标点符号和字母大写

import collections
import re
from d2l import torch as d2l

#@save
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt',
                                '090b5e7e70c295757f55df93cb0a180b9691891a')

def read_time_machine():   #@save
    with open(d2l.download('time_machine'), 'r') as f:
        lines = f.readlines()
    return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]

lines = read_time_machine()
print(f'文本总行数:{len(lines)}')
print(lines[0])
print(lines[10])

2.2 词元化

下面的tokenize函数将文本行列表(lines)作为输入,列表中的每个元素是一个文本序列(如一条文本行)。 每个文本序列又被拆分成一个词元列表,词元(token)是文本的基本单位。最后,返回一个由词元列表组成 的列表,其中的每个词元都是一个字符串(string)。

def tokenize(lines, token='word'):   #@save
    if token == 'word':
        return [line.split() for line in lines]
    elif token == 'char':
        return [list(line) for line in lines]
    else:
        print('error: 未知词元类型:' + token)
    
tokens = tokenize(lines)
for i in range(11):
    print(tokens[i])

2.3 词表

词元的类型是字符串,而模型需要的输入是数字,因此这种类型不方便模型使用。现在,让我们构建一个字典,通常也叫做词表(vocabulary),用来将字符串类型的词元映射到从0开始的数字索引中。我们先将训练 集中的所有文档合并在一起,对它们的唯一词元进行统计,得到的统计结果称之为语料(corpus)。然后根 据每个唯一词元的出现频率,为其分配一个数字索引。很少出现的词元通常被移除,这可以降低复杂性。另 外,语料库中不存在或已删除的任何词元都将映射到一个特定的未知词元“”。我们可以选择增加一个 列表,用于保存那些被保留的词元,例如:填充词元(“”);序列开始词元(“”);序列结束词元 (“”)。

class Vocab:   #@save
    def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):
        if tokens is None:
            tokens = []
        if reserved_tokens is None:
            reserved_tokens = []
        counter = count_corpus(tokens)
        self._token_freqs = sorted(counter.items(), key=lambda x: x[1], reverse=True)
        self.idx_to_token = ['<unk>'] + reserved_tokens
        self.token_to_idx = {token: idx for idx, token in enumerate(self.idx_to_token)}
        for token, freq in self._token_freqs:
            if freq < min_freq:
                break
            if token not in self.token_to_idx:
                self.idx_to_token.append(token)
                self.token_to_idx[token] = len(self.idx_to_token) - 1
                
    def __len__(self):
        return len(self.idx_to_token)
    
    def __getitem__(self, tokens):
        if not isinstance(tokens, (list, tuple)):
            return self.token_to_idx.get(tokens, self.unk)
        return [self.__getitem__(token) for token in tokens]
    
    @property
    def unk(self):
        return 0
    
    @property
    def token_freqs(self):
        return self._token_freqs
    
def count_corpus(tokens):  #@save
    if len(tokens) == 0 or isinstance(tokens[0], list):
        tokens = [token for line in tokens for token in line]
    return collections.Counter(tokens)

vocab = Vocab(tokens)
print(list(vocab.token_to_idx.items())[:10])

for i in [0, 10]:
    print('文本:', tokens[i])
    print('索引:', vocab[tokens[i]])

2.4 整合所有功能

在使用上述函数时,我们 将所有功能打包到 load_corpus_time_machine 函数中,该函数返回corpus(词元索 引列表)和vocab(时光机器语料库的词表)。我们在这里所做的改变是:

  1. 为了简化后面章节中的训练,我们使用字符(而不是单词)实现文本词元化
  2. 时光机器数据集中的每个文本行不一定是一个句子或一个段落,还可能是一个单词,因此返回 的corpus仅处理为单个列表,而不是使用多词元列表构成的一个列表。
def load_corpus_time_machine(max_tokens=-1):    #@save
    lines = read_time_machine()
    tokens = tokenize(lines, 'char')
    vocab = Vocab(tokens)
    corpus = [vocab[token] for line in tokens for token in line]
    if max_tokens > 0:
        corpus = corpus[:max_tokens]
    return corpus, vocab

corpus, vocab = load_corpus_time_machine()
len(corpus), len(vocab)   # (170580, 28)

小结:

  • 文本是序列数据 的一种最常见的形式之一。
  • 为了对文本进行预处理,我们 通常将文本拆分为词元,构建词表 将词元字符串映射为数字 索引,并 将文本数据转换为词元 索引以供模型操作。

三 语言模型和数据集

在给定这样的文本序列时,语言模型(language model)的目标是 估计序列的联合概率

为了训练语言模型,我们需要计算单词的概率,以及给定前面几个单词后出现某个单词的条件概率。这些概 率本质上就是语言模型的参数。

这里,我们假设训练数据集是一个大型的文本语料库。比如,维基百科的所有条目、古登堡计划101,或者 所有发布在网络上的文本。训练数据集中词的概率可以根据给定词的相对词频来计算。

一种常见的策略是执行某种形式的 拉普拉斯平滑(Laplace smoothing),具体方法是在所有计数中添加一个 小常量。

3.1 自然语言统计

我们看看在真实数据上如果进行自然语言统计。根据 8.2节中介绍的时光机器数据集构建词表,并打印前10个 最常用的(频率最高的)单词。

import random
import torch
from d2l import torch as d2l

tokens = d2l.tokenize(d2l.read_time_machine())
# 因为每个文本行不一定是一个句子或一个段落,因此我们把所有文本行拼接到一起
corpus = [token for line in tokens for token in line]
vocab = d2l.Vocab(corpus)
vocab.token_freqs[:10]

正如我们所看到的,最流行的词看起来很无聊,这些词通常被称为 停用词(stop words),因此可以被过滤掉。 尽管如此,它们本身仍然是有意义的,我们仍然会在模型中使用它们。

freqs = [freq for token, freq in vocab.token_freqs]
d2l.plot(freqs, xlabel='token: x', ylabel='frequency: n(x)',
         xscale='log', yscale='log')

bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])]
bigram_vocab = d2l.Vocab(bigram_tokens)
bigram_vocab.token_freqs[:10]

trigram_tokens = [triple for triple in zip(corpus[:-2], corpus[1:-1], corpus[2:])]
trigram_vocab = d2l.Vocab(trigram_tokens)
trigram_vocab.token_freqs[:10]

bigram_freqs = [freq for token, freq in bigram_vocab.token_freqs]
trigram_freqs = [freq for token, freq in trigram_vocab.token_freqs]
d2l.plot([freqs, bigram_freqs, trigram_freqs], xlabel='token: x',
         ylabel='frequency: n(x)', xscale='log', yscale='log',
         legend=['unigram', 'bigram', 'trigram'])

小结:

  • 语言模型 是自然语言处理的关键。
  • n元语法 通过截断相关性,为处理长序列提供了一种实用的模型。
  • 长序列存在一个问题:它们很少出现或者从不出现
  • 齐普夫定律 支配着单词的分布,这个分布不仅适用于一元语法,还适用于其他n元语法。
  • 通过 拉普拉斯平滑法 可以有效地处理 结构丰富而频率不足的低频词词组
  • 读取长序列的主要方式是 随机采样和顺序分区。在迭代过程中,后者可以保证来自两个相邻的小批量中的子序列在原始序列上也是相邻的

四 循环神经网络

我们介绍了n元语法模型,其中单词xt在时间步t的条件概率仅取决于前面n − 1个单词。对于时 间步t − (n − 1)之前的单词,如果我们想将其可能产生的影响合并到xt上,需要增加n,然而 模型参数的数量也会随之呈指数增长,因为词表V需要存储|V|n个数字,因此与其将P(xt | xt−1, . . . , xt−n+1)模型化,不如使用 隐变量模型

4.1 无隐状态的神经网络

让我们来看一看只有 单隐藏层的多层感知机。设隐藏层的激活函数为ϕ,给定一个小批量样本X ∈ R n×d,其 中批量大小为n,输入维度为d,则隐藏层的输出H ∈ R n×h通过下式计算。

         H = \varnothing (XW_{xh} + b_h)

4.2 有隐状态的循环神经网络

有了隐状态后,情况就完全不同了。假设我们在时间步t有小批量输入Xt ∈ R n×d。换言之,对于n个序列样本 的小批量,Xt的每一行对应于来自该序列的时间步t处的一个样本。接下来,用Ht ∈ R n×h 表示时间步t的隐 藏变量。与多层感知机不同的是,我们在这里保存了前一个时间步的隐藏变量Ht−1,并引入了一个新的权重 参数Whh ∈ R h×h,来描述如何在当前时间步中使用前一个时间步的隐藏变量。具体地说,当前时间步隐藏 变量由当前时间步的输入与前一个时间步的隐藏变量一起计算得出。

        H_t = \varnothing (X_tW_{xh} + H_{t-1}W_{hh}+ b_h)

比无隐状态相比多添加了一项H_{t-1}W_{hh},从而实例化了。从相邻时间步的隐藏变量Ht和 Ht−1之 间的关系可知,这些变量 捕获并保留了序列直到其当前时间步的历史信息,就如当前时间步下神经网络的状 态或记忆,因此这样的隐藏变量被称为隐状态(hidden state)。由于在当前时间步中,隐状态使用的定义与 前一个时间步中使用的定义相同,因此的计算是循环的(recurrent)。于是基于循环计算的隐状态神 经网络被命名为 循环神经网络(recurrent neural network)。在循环神经网络中执行计算的层称为循环层(recurrent layer)。

import torch
from d2l import torch as d2l

X, W_xh = torch.normal(0, 1, (3, 1)), torch.normal(0, 1, (1, 4))
H, W_hh = torch.normal(0, 1, (3, 4)), torch.normal(0, 1, (4, 4))
torch.matmul(X, W_xh) + torch.matmul(H, W_hh)
# tensor([[ 1.2156,  0.6346, -2.1131, -1.5739],
#         [-1.5890, -0.7256,  1.1961, -0.7566],
#         [-1.9251, -1.0113,  0.1320,  1.8175]])

torch.matmul(torch.cat((X, H), 1), torch.cat((W_xh, W_hh), 0))
# tensor([[-3.8297, -2.1686,  1.9846,  6.5896],
#         [ 3.0873,  4.4307, -1.2372, -2.8524],
#         [ 2.6609,  0.1703, -0.0343, -3.5691]])

小结:

  • 对隐状态使用 循环计算的神经网络称为 循环神经网络(RNN)。
  • 循环神经网络的隐状态 可以捕获直到当前时间步序列 的历史信息。
  • 循环神经网络模型的参数数量不会随着时间步的增加而增加
  • 我们可以使用循环神经网络创建 字符级语言模型
  • 我们可以使用 困惑度 来评价 语言模型的质量。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

85、动态规划-零钱兑换

思路&#xff1a; 还是老样子&#xff0c;还是先使用递归方式来解&#xff0c;然后通过递归推动态规划。那递归如何设计? 定义一个递归方法&#xff1a;表示从index开始到N达到剩下的值&#xff08;目标值减去上一步的值&#xff09;做少可以得到数量是多少。int process(in…

快速幂笔记

快速幂即为快速求出一个数的幂&#xff0c;这样可以避免TLE&#xff08;超时&#xff09;的错误。 传送门&#xff1a;快速幂模板 前置知识&#xff1a; 1) 又 2) 代码&#xff1a; #include <bits/stdc.h> using namespace std; int quickPower(int a, int b) {int…

STM32单片机wifi云平台+温度+烟雾+火焰+短信+蜂鸣器 源程序原理图

目录 1. 整体设计 2. 液晶显示 3. Ds18b20温度传感器 4. Mq2烟雾传感器 5. 火焰传感器传感器 6. 蜂鸣器驱动控制 7. 按键 8. Gsm短信模块 9. Esp8266wifi模块 10、源代码 11、资料内容 资料下载地址&#xff1a;STM32单片机wi…

PR2019新建项目教程

一&#xff0c;新建项目&#xff1a; 设置工程名称&#xff0c;选择工程目录位置&#xff0c;其他默认&#xff1a; 二&#xff0c;新建序列 新建项->序列&#xff1a; 设置序列参数&#xff1a; 三&#xff0c;导出设置 设置导出参数&#xff1a;

vivado Aurora 8B/10B IP核(11)- 共享逻辑(Share Logic)

Vivado IDE 中的共享逻辑选项配置核心&#xff0c;包括可收集的资源&#xff0c;如收发器四路 PLL&#xff08;QPLL&#xff09;&#xff0c;收 发器差分缓冲区&#xff08;IBUFDS_GTE2&#xff09;以及核心或示例设计中的时钟和复位逻辑。 当选择了核心选项中的包含共享逻辑时…

QT防止自研软件被复制的基本操作(二)

参考一 自研软件为了防止被人任意复制传播&#xff0c;需要设置注册使用模式。基本原理&#xff1a;通过计算机的特异性编号&#xff0c;加上自己的编码&#xff0c;使用加密算法算出一个生成码。 一、计算机的特异性编号 硬盘的编号&#xff1a;最后一块硬盘的编号就行&#…

linux的基础入门(2)

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

开源AI名片商城小程序:打造卓越客户体验,引领留存与增长新潮流

在快速变化的商业环境中&#xff0c;如何有效地吸引并留住客户已成为每个企业面临的重要课题。为此&#xff0c;我们精心打造了一款创新的解决方案——开源AI名片商城小程序&#xff0c;它将内容培育、社交互动、产品展示与购买功能融为一体&#xff0c;为您的客户带来前所未有…

PS超美古风毛笔字体300款,PS毛笔字体笔触与笔刷素材

一、素材描述 本套PS素材&#xff0c;大小1.66G&#xff0c;2个压缩文件。 二、素材目录 &#xff08;一&#xff09;、PS毛笔字体笔触与笔刷素材.rar&#xff08;163.5M&#xff09; &#xff08;二&#xff09;、PS超美古风毛笔字体300款.rar&#xff08;1.5G&#xff09…

2.1 上海雷卯电子PLC

PLC&#xff08;可编程逻辑控制器&#xff09;像是工厂自动化系统的“大脑”&#xff0c;负责监控和控制各种生产过程。PLC 能够精确地协调各类设备的操作&#xff0c;实现生产流程的自动化和优化。通过编程&#xff0c;它可以根据不同的生产需求灵活调整控制逻辑&#xff0c;提…

【数据结构】这里有一份KMP算法优化的详细攻略,不要错过哦!!!

KMP算法优化 导读一、C语言实现next数组1.1 next数组的底层逻辑1.2 串的数据类型的选择1.3 函数三要素1.3.1 查漏补缺——数组传参1.3.2 小结 1.4 函数主体1.4.1 通过模拟实现1.4.1.1 算法思路1.4.1.2 代码实现1.4.1.3 算法测试 1.4.2 算法的缺陷1.4.3 算法优化1.4.3.1 算法思路…

蛇鹭优化算法(SBOA)-2024年4月SCI新算法-公式原理详解与性能测评 Matlab代码免费获取

声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 原理简介 一、初始化阶段 二、蛇鹭的捕猎…

CSS精灵图、字体图标、HTML5新增属性、界面样式和网站 favicon 图标

精灵图 为什么要使用精灵图 一个网页中往往会应用很多小的背景图像作为修饰&#xff0c;当网页中的图像过多时&#xff0c;服务器就会频繁地接收和发送请求图片&#xff0c;造成服务器请求压力过大&#xff0c;这将大大降低页面的加载速度,因此&#xff0c;为了有效地减少服务…

C++校招八股

c类的访问权限与继承方式 公有成员在任何地方都可以被访问&#xff0c;包括类的外部和派生类。受保护成员在类的内部和派生类中可以被访问&#xff0c;但在类的外部不可访问。 私有成员只能在类的内部访问&#xff0c;包括类的成员函数和友元函数&#xff0c;不允许在类的外部…

应用分层和企业规范

目录 一、应用分层 1、介绍 &#xff08;1&#xff09;为什么需要应用分层&#xff1f; &#xff08;2&#xff09;如何分层&#xff1f;&#xff08;三层架构&#xff09; MVC 和 三层架构的区别和联系 高内聚&#xff1a; 低耦合&#xff1a; 2、代码重构 controlle…

【软件测试】测试用例设计方法

1. 等价类划分法1.1. 等价类划分法的定义1.2. 有效等价类和无效等价类1.3. 等价类划分法实例分析 2. 边界值分析法2.1. 边界值分析法的定义2.2. 边界点2.3. 边界值法实例分析 3. 判定表法3.1. 如何用判定表法设计测试用例3.2. 判定表法实例分析 4. 正交表法4.1. 什么是正交表4.…

模拟实现memcpy,memmove,memset,memcmp

memcpy void * memcpy ( void * destination, const void * source, size_t num ); 使用注意事项&#xff1a; 从source的位置向后复制num个字节数据到destination所指向的内存位置中。 这个函数遇到如果源空间和⽬标空间出现重叠&#xff0c;就得使⽤memmove函数处理。 …

纯血鸿蒙APP实战开发——自定义路由栈管理

介绍 本案例将介绍如何使用路由跳转返回时获取到来源页的模块名以及路径名&#xff0c;在实际场景中同一页面通常会根据不同来源页展示不同的UI。 使用说明 无特殊使用说明&#xff0c;其他使用说明参考动态路由的相关说明 实现思路 路由来源页的实现 新增来源页字段 ex…

【刷题篇】动态规划-二维费用的背包问题(十二)

文章目录 1、一和零2、盈利计划3、组合总和 Ⅳ4、不同的二叉搜索树(卡特兰数) 1、一和零 给你一个二进制字符串数组 strs 和两个整数 m 和 n 。 请你找出并返回 strs 的最大子集的长度&#xff0c;该子集中 最多 有 m 个 0 和 n 个 1 。 如果 x 的所有元素也是 y 的元素&#x…

vue快速入门(五十三)使用js进行路由跳转

注释很详细&#xff0c;直接上代码 上一篇 新增内容 几种常用的路由跳转方式演示 源码 App.vue <template><div id"app"><div class"nav"><!-- router-link 自带两个高亮样式类 router-link-exact-active和router-link-active区别&a…