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

到目前为止,我们遇到过两种类型的数据:表格数据和图像数据。对于图像数据,我们设计了专门的卷积神经网络架构(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 d2lT = 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 netloss = 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():   #@savewith 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'):   #@saveif 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:   #@savedef __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_tokensself.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:breakif token not in self.token_to_idx:self.idx_to_token.append(token)self.token_to_idx[token] = len(self.idx_to_token) - 1def __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]@propertydef unk(self):return 0@propertydef token_freqs(self):return self._token_freqsdef count_corpus(tokens):  #@saveif 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):    #@savelines = 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, vocabcorpus, 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 d2ltokens = 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 d2lX, 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.mzph.cn/bicheng/6484.shtml

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

相关文章

算法课程笔记——蓝桥云课第六次直播

&#xff08;只有一个数&#xff0c;或者因子只有一个&#xff09;先自己打表&#xff0c;找找规律函数就是2的n次方 异或前缀和 相等就抵消 先前缀和再二分

【Python】机器学习之Sklearn基础教程大纲

机器学习之Sklearn基础教程大纲 1. 引言 机器学习简介Scikit-learn&#xff08;Sklearn&#xff09;库介绍安装和配置Sklearn 2. 数据预处理 2.1 数据加载与查看 - 加载CSV、Excel等格式的数据- 查看数据的基本信息&#xff08;如形状、数据类型等&#xff09;2.2 数据清洗…

本地部署大模型ollama+docker+open WebUI/Lobe Chat

文章目录 大模型工具Ollama下载安装运行Spring Ai 代码测试加依赖配置写代码 ollama的web&Desktop搭建部署Open WebUI有两种方式Docker DesktopDocker部署Open WebUIDocker部署Lobe Chat可以配置OpenAI的key也可以配置ollama 大模型的选择 本篇基于windows环境下配置 大模型…

翔云优配恒生指数涨1.85%、恒生科技指数涨3.74% 小鹏汽车涨超8%

5月3日港股开盘&#xff0c;恒生指数涨1.85%&#xff0c;报18543.3点&#xff0c;恒生科技指数涨3.74%&#xff0c;报4009.96点&#xff0c;国企指数涨2.23%&#xff0c;报6580.81点&#xff0c; 翔云优配是一家领先的在线投资平台,提供全球范围内的股票、期货、基金等交易服务…

小程序引入 Vant Weapp 极简教程

一切以 Vant Weapp 官方文档 为准 Vant Weapp 官方文档 - 快速入手 1. 安装nodejs 前往官网下载安装即可 nodejs官网 安装好后 在命令行&#xff08;winr&#xff0c;输入cmd&#xff09;输入 node -v若显示版本信息&#xff0c;即为安装成功 2. 在 小程序根目录 命令行/终端…

C++类的小结

1、类定义 使用class关键字定义类。 类名通常以大写字母开头&#xff0c;以符合命名规范。 类包含成员变量&#xff08;也称为属性或数据成员&#xff09;和成员函数&#xff08;也称为方法或行为&#xff09;。 class MyClass { public: int x; // 数据成员 void setX…

【Gateway远程开发】0.5GB of free space is necessary to run the IDE.

【Gateway远程开发】0.5GB of free space is necessary to run the IDE. 报错 0.5GB of free space is necessary to run the IDE. Make sure that there’s enough space in following paths: /root/.cache/JetBrains /root/.config/JetBrains 原因 下面两个路径的空间不…

【OpenNJet下一代云原生之旅】

OpenNJet下一代云原生之旅 1、OpenNJet的定义OpenNJet架构图 2、OpenNJet的特点性能无损动态配置灵活的CoPilot框架支持HTTP/3支持国密企业级应用高效安全 3、OpenNJet的功能特性4、OpenNJet的安装使用编译安装配置yum源创建符号连接修改配置编译 5、通过 OpenNJet 部署 WEB SE…

基于OpenCv的图像特征点检测

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

【设计模式】函数式编程范式工厂模式(Factory Method Pattern)

目录标题 定义函数式接口函数式接口实现类工厂类封装实际应用总结 定义函数式接口 ISellIPad.java /*** 定义一个函数式接口* param <T>*/ FunctionalInterface public interface ISellIPad<T> {T getSellIPadInfo();}函数式接口实现类 HuaWeiSellIPad.java pu…

rust数据类型转换,as和TryInto使用

Rust 是类型安全的语言&#xff0c;因此在 Rust 中做类型转换不是一件简单的事&#xff0c;这一章节我们将对 Rust 中的类型转换进行详尽讲解。 as转换 先来看一段代码&#xff1a; fn main() {let a: i32 10;let b: u16 100;if a < b {println!("Ten is less tha…

无U盘基于本地硬盘无损制作虚拟U盘(Windows、Linux系统安装启动盘)

知识点 实验环境 名称版本使用平台Win11本地硬盘格式GPT待安装镜像deepin-desktop-community-20.9-amd64.iso 文中工具下载链接&#xff1a; https://download.csdn.net/download/xzzteach/89263714 deepin-desktop-community-20.9-amd64.iso 文件结构如下&#xff1a; 在Li…

多功能在线二维码生成源码

上传即可使用&#xff0c;可以把电子名片、文本、wifi网络、电子邮件、短信、电话号码、网址等信息生成对应的二维码图片。 多功能在线二维码生成源码

10G MAC层设计系列-(4)MAC TX模块

一、前言 MAC TX模块就是要将IP层传输过来的数据封装前导码、MAC地址、帧类型以及进行CRC校验&#xff0c;并与CRC值一块组成以太网帧。 二、模块设计 首先对输入的数据进行缓存&#xff0c;原因是在之后要进行封装MAC帧头&#xff0c;所以需要控制数据流的流动 FIFO_DATA_6…

Linux内核深入学习 - 中断与异常(上)

中断与异常 中断通常被定义为一个事件&#xff1a;让事件改变处理器执行的指令顺序这样的事件&#xff0c;与CPU芯片内外部硬件电路产生的电信号相对应&#xff01; 中断通常分为同步中断与异步中断&#xff1a; 同步中断指的是当指令执行时&#xff0c;由CPU控制单元产生的…

力扣hot100:543. 二叉树的直径/108. 将有序数组转换为二叉搜索树

一、543. 二叉树的直径 LeetCode&#xff1a;543. 二叉树的直径 二叉树的直径 二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。 遇到二叉树的问题很容易去直接用求解的目标去定义递归函数。但是仔细考虑&#xff0c;返回树的直径并不能向上传播。因此我们可以拆…

2024抖音直播带货-直播间拆解:抖店运营从入门到精通(56节课)

起号原理方式以及节点处理 类目的选择选品思路 付费流量投放原理 直播间进阶玩法 课程内容 直播间搭建标准自然起号(0-1)原理 方式 以及节点处理 老号重启(0-1)原理 方式 以及节点处理 账号在线人数稳定 原理 方式 以及节点处理 账号销售额放大 原理 方式 以及节点处理…

IoTDB 入门教程 基础篇⑨——TsFile导入导出工具

文章目录 一、前文二、准备2.1 准备导出服务器2.2 准备导入服务器 三、导出3.1 导出命令3.2 执行命令3.3 tsfile文件 四、导入4.1 上传tsfile文件4.2 导入命令4.3 执行命令 五、查询六、参考 一、前文 IoTDB入门教程——导读 数据库备份与迁移是数据库运维中的核心任务&#xf…

内核workqueue框架

workqueue驱动的底半部实现方式之一就是工作队列&#xff0c;作为内核的标准模块&#xff0c;它的使用接口也非常简单&#xff0c;schedule_work或者指定派生到哪个cpu的schedule_work_on。 还有部分场景会使用自定义的workqueue&#xff0c;这种情况会直接调用queue_work和qu…

探索C++模板类的奥秘:从基础到实践的深度之旅

目录 引言 一、模板类基础 A. 何为模板类&#xff1f; B. 模板类与普通类的区别 二、模板类实例化及特化 C. 实例化模板类 D. 模板类特化 三、模板类的应用与实战 E. 示例分析&#xff1a;栈模板类实现 结论 引言 在C编程的世界里&#xff0c;模板这一特性如同魔法般…