NLP Step by Step -- 如何微调一个模型(1)

文章目录

  • 数据处理
    • 从模型中心(Hub)加载数据集
    • 预处理数据集
    • 动态填充
  • 使用 Trainer API 微调模型
    • Training
    • 评估

数据处理

这一小节学习第一小节中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子:

import torch
from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification# Same as before
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequences = ["I've been waiting for a HuggingFace course my whole life.","This course is amazing!",
]
batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt")# This is new
batch["labels"] = torch.tensor([1, 1])optimizer = AdamW(model.parameters())
loss = model(**batch).loss
loss.backward()
optimizer.step()

当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。

在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在这篇文章发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。

从模型中心(Hub)加载数据集

模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击数据集的链接即可进行浏览。我们建议您在阅读本节后阅读一下加载和处理新的数据集这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的GLUE 基准测试数据集,它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。

🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集:

from datasets import load_datasetraw_datasets = load_dataset("glue", "mrpc")
raw_datasets
## 
DatasetDict({train: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx'],num_rows: 3668})validation: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx'],num_rows: 408})test: Dataset({features: ['sentence1', 'sentence2', 'label', 'idx'],num_rows: 1725})
})

正如你所看到的,我们获得了一个DatasetDict对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(sentence1, sentence2, label, and idx)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。

默认情况下,此命令在下载数据集并缓存到 ~/.cache/huggingface/datasets. 回想一下第2章,您可以通过设置HF_HOME环境变量来自定义缓存的文件夹。

我们可以访问我们数据集中的每一个raw_train_dataset对象,如使用字典:

raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
{'idx': 0,'label': 1,'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .','sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}

我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看raw_train_dataset的features. 这将告诉我们每列的类型:

raw_train_dataset.features
{'sentence1': Value(dtype='string', id=None),'sentence2': Value(dtype='string', id=None),'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),'idx': Value(dtype='int32', id=None)}

在上面的例子之中,Label(标签) 是一种ClassLabel(分类标签),使用整数建立起到类别标签的映射关系。0对应于not_equivalent,1对应于equivalent。

预处理数据集

为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在第二章上看到的那样

from transformers import AutoTokenizer
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"])
tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"])

然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理:

inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs{ 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
}

我们在第二章 讨论了输入词id(input_ids) 和 注意力遮罩(attention_mask) ,但我们在那个时候没有讨论类型标记ID(token_type_ids)。在这个例子中,类型标记ID(token_type_ids)的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。

如果我们将input_ids中的id转换回文字:

tokenizer.convert_ids_to_tokens(inputs["input_ids"])
## 我们将得到:
['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']

所以我们看到模型需要输入的形式是 [CLS] sentence1 [SEP] sentence2 [SEP]。因此,当有两句话的时候。类型标记ID(token_type_ids) 的值是:

['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]']
[      0,      0,    0,     0,       0,          0,   0,       0,      1,    1,     1,        1,     1,   1,       1]

如您所见,输入中 [CLS] sentence1 [SEP] 它们的类型标记ID均为0,而其他部分,对应于sentence2 [SEP],所有的类型标记ID均为1.

请注意,如果选择其他的检查点,则不一定具有类型标记ID(token_type_ids)(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。

用类型标记ID对BERT进行预训练,并且使用第一章的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。

在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。

一般来说,你不需要担心是否有类型标记ID(token_type_ids)。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。

现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如之前的章节,我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在第二章中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是:

tokenized_dataset = tokenizer(raw_datasets["train"]["sentence1"],raw_datasets["train"]["sentence2"],padding=True,truncation=True,
)

这很有效,但它的缺点是返回字典(字典的键是输入词id(input_ids) , 注意力遮罩(attention_mask) 和 类型标记ID(token_type_ids),字典的值是键所对应值的列表)。而且只有当您在转换过程中有足够的内存来存储整个数据集时才不会出错(而🤗数据集库中的数据集是以Apache Arrow文件存储在磁盘上,因此您只需将接下来要用的数据加载在内存中,因此会对内存容量的需求要低一些)。

为了将数据保存为数据集,我们将使用Dataset.map()方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数:

def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)

此函数的输入是一个字典(与数据集的项类似),并返回一个包含输入词id(input_ids) , 注意力遮罩(attention_mask) 和 类型标记ID(token_type_ids) 键的新字典。请注意,如果像上面的示例一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用map()使用该选项 batched=True ,这将显著加快标记与标记的速度。这个标记器来自🤗 Tokenizers库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。

请注意,我们现在在标记函数中省略了padding参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力!

下面是我们如何在所有数据集上同时应用标记函数。我们在调用map时使用了batch =True,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多

tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
tokenized_datasets

🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键:

DatasetDict({train: Dataset({features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],num_rows: 3668})validation: Dataset({features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],num_rows: 408})test: Dataset({features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],num_rows: 1725})
})

在使用预处理函数map()时,甚至可以通过传递num_proc参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用num_proc可能会加快预处理。

我们的标记函数(tokenize_function)返回包含输入词id(input_ids) , 注意力遮罩(attention_mask) 和 类型标记ID(token_type_ids) 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数map()为现有键返回一个新值,那将会修改原有键的值。

最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。

动态填充

负责在批处理中将数据整理为一个batch的函数称为collate函数。它是你可以在构建DataLoader时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。

为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过DataCollatorWithPadding为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切:

from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列idx, sentence1和sentence2,因为不需要它们,并查看一个batch中每个条目的长度:

samples = tokenized_datasets["train"][:8]
samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]}
[len(x) for x in samples["input_ids"]][50, 59, 47, 67, 59, 50, 62, 32]

毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查data_collator是否正确地动态填充了这批样本:

batch = data_collator(samples)
{k: v.shape for k, v in batch.items()}{'attention_mask': torch.Size([8, 67]),'input_ids': torch.Size([8, 67]),'token_type_ids': torch.Size([8, 67]),'labels': torch.Size([8])}

使用 Trainer API 微调模型

🤗 Transformers提供了一个 Trainer 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 Trainer .最难的部分可能是为 Trainer.train()配置运行环境,因为它在 CPU 上运行速度会非常慢。

下面的示例假设您已经执行了上一节中的示例。下面这段代码,概括了您需要提前运行的代码:

from datasets import load_dataset
from transformers import AutoTokenizer, DataCollatorWithPaddingraw_datasets = load_dataset("glue", "mrpc")
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)def tokenize_function(example):return tokenizer(example["sentence1"], example["sentence2"], truncation=True)tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

Training

在我们定义我们的 Trainer 之前首先要定义一个 TrainingArguments 类,它将包含 Trainer用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。

from transformers import TrainingArgumentstraining_args = TrainingArguments("test-trainer")

第二步是定义我们的模型。我们将使用 AutoModelForSequenceClassification 类,它有两个参数:

from transformers import AutoModelForSequenceClassificationmodel = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)

你会注意到,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。
一旦我们有了我们的模型,我们就可以定义一个 Trainer 通过将之前构造的所有对象传递给它——我们的model 、training_args ,训练和验证数据集,data_collator ,和 tokenizer :

from transformers import Trainertrainer = Trainer(model,training_args,train_dataset=tokenized_datasets["train"],eval_dataset=tokenized_datasets["validation"],data_collator=data_collator,tokenizer=tokenizer,
)

请注意,当您在这里完成tokenizer后,默认 Trainer使用 的data_collator会使用之前预定义的 DataCollatorWithPadding ,因此您可以在这个例子中跳过 data_collator=data_collator。
为了让预训练模型在在我们的数据集上微调,我们只需要调用Trainer的train() 方法 :

trainer.train()

这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为:
我们没有通过将evaluation_strategy设置为“steps”(在每次更新参数的时候评估)或“epoch”(在每个epoch结束时评估)来告诉Trainer在训练期间进行评估。
我们没有为Trainer提供一个compute_metrics()函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。

评估

让我们看看如何构建一个有用的 compute_metrics() 函数并在我们下次训练时使用它。该函数必须采用 EvalPrediction 对象(带有 predictions 和 label_ids 字段的参数元组)并将返回一个字符串到浮点数的字典(字符串是返回的指标的名称,而浮点数是它们的值)。我们可以使用 Trainer.predict() 命令来使用我们的模型进行预测:

predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)(408, 2) (408,)

predict() 的输出结果是具有三个字段的命名元组: predictions , label_ids , 和 metrics .这 metrics 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 compute_metrics() 函数并将其传递给 Trainer ,该字段还将包含compute_metrics()的结果。

predict() 方法是具有三个字段的命名元组: predictions , label_ids , 和 metrics .这 metrics 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 compute_metrics() 函数并将其传递给 Trainer ,该字段还将包含compute_metrics() 的结果。如你看到的, predictions 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给predict()的数据集的每个元素的结果(logits)(正如你在之前的章节看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引:

import numpy as nppreds = np.argmax(predictions.predictions, axis=-1)

现在建立我们的 compute_metric() 函数来较为直观地评估模型的好坏,我们将使用 🤗 Evaluate 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 evaluate.load() 函数。返回的对象有一个 compute()方法我们可以用来进行度量计算的方法:

import evaluatemetric = evaluate.load("glue", "mrpc")
metric.compute(predictions=preds, references=predictions.label_ids)
Copied
{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542}

您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在BERT 论文中展示的基础模型的 F1 分数为 88.9。那是 uncased 模型,而我们目前正在使用 cased 模型,通过改进得到了更好的结果。

最后将所有东西打包在一起,我们得到了我们的 compute_metrics() 函数:

def compute_metrics(eval_preds):metric = evaluate.load("glue", "mrpc")logits, labels = eval_predspredictions = np.argmax(logits, axis=-1)return metric.compute(predictions=predictions, references=labels)

为了查看模型在每个训练周期结束的好坏,下面是我们如何使用compute_metrics()函数定义一个新的 Trainer :

training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch")
model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2)trainer = Trainer(model,training_args,train_dataset=tokenized_datasets["train"],eval_dataset=tokenized_datasets["validation"],data_collator=data_collator,tokenizer=tokenizer,compute_metrics=compute_metrics,
)

请注意,我们设置了了一个新的 TrainingArguments 它的evaluation_strategy 设置为 epoch 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行:

trainer.train()

这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。

这 Trainer 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 fp16 = True )。

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

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

相关文章

Docker 快速入门

Docker 是一个开源的应用容器引擎,它允许开发者打包他们的应用以及应用的依赖包到一个轻量级、可移植的容器中,然后发布到任何支持 Docker 的系统上。以下是 Docker 入门的详细步骤: 1. 了解 Docker 的基本概念 在开始之前,了解…

redis运维篇上篇

最近在学redis,由于笔者是学运维的,所以推荐学习运维的小伙伴参考,希望对大家有帮助! redis运维篇下篇:http://t.csdnimg.cn/83sQ1 附加redis多用户管理:http://t.csdnimg.cn/DY3yx 目录 一.安装redis 二.redis配置调优 三.启…

【Excel】excel计算相关性系数R、纳什效率系数NSE、Kling-Gupta系数KGE

对于采用的数据: B2:B10958是观测值的所在范围 C2:C10958是模型计算值的所在范围 一、相关系数R是用来衡量两个变量之间线性关系强度和方向的统计量。在水文学和气象学中,常用的相关系数是皮尔逊相关系数(Pearson correlation coefficient&am…

智能体可靠性的革命性提升,揭秘知识工程领域的参考架构新篇章

引言:知识工程的演变与重要性 知识工程(Knowledge Engineering,KE)是一个涉及激发、捕获、概念化和形式化知识以用于信息系统的过程。自计算机科学和人工智能(AI)历史以来,知识工程的工作流程因…

【数据结构(邓俊辉)学习笔记】向量05——排序器

文章目录 0. 概述1.统一入口2. 起泡排序2.1 起泡排序(基础版)2.1.1 算法分析2.1.2 算法实现2.1.3 重复元素与稳定性2.1.4 复杂度分析 3. 归并排序3.1 有序向量的二路归并3.2 分治策略3.3 实例3.4 二路归并接口的实现3.5 归并时间3.6 排序时间 4.综合评价…

基于Matlab使用深度学习的多曝光图像融合

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 在图像处理领域,多曝光图像融合技术是一种重要的技术,它可以将不同曝光条件下…

备忘录模式(行为型)

目录 一、前言 二、备忘录模式 三、总结 一、前言 备忘录模式(Memento Pattern)是一种行为型设计模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在之后将该对象恢复到原…

idea生成双击可执行jar包

我这里是一个生成xmind,解析sql的一个main方法,可以通过配置文件来修改有哪些类会执行 我们经常会写一个处理文件的main方法,使用时再去寻找,入入会比较麻烦,这里就可以把我们写过的main方法打成jar包,放到指定的目录来处理文件并生成想要的结果 1.写出我们自己的main方法,本地…

C语言.自定义类型:结构体

自定义类型:结构体 1.结构体类型的声明1.1结构体回顾1.1.1结构体的声明1.1.2结构体变量的创建和初始化 1.2结构体的特殊声明1.3结构体的自引用 2.结构体内存对齐2.1对齐规则2.2为什么存在内存对齐2.3修改默认对齐数 3.结构体传参4.结构体实现位段4.1什么是位段4.2位…

深入浅出DBus-C++:Linux下的高效IPC通信

目录标题 1. DBus简介2. DBus-C的优势3. 安装DBus-C4. 使用DBus-C初始化和连接到DBus定义接口和方法发送和接收信号 5. dbus-cpp 0.9.0 的安装6. 创建一个 DBus 服务7. 客户端的实现8. 编译和运行你的应用9. 瑞芯微(Rockchip)的 Linux 系统通常会自带 db…

OpenLayers入门①(引入的是一个高德地图)

OpenLayers入门&#xff08;一&#xff09; - 知乎 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport&qu…

使用RTSP将笔记本摄像头的视频流推到开发板

一、在Windows端安装ffmpeg 1. 下载ffmpeg:下载ffmpeg 解压ffmpeg-master-latest-win64-gpl.zip bin 目录下是 dll 动态库 , 以及 可执行文件 ;将 3 33 个可执行文件拷贝到 " C:\Windows " 目录下 ,将所有的 " .dll " 动态库拷贝到 " C:\Windows\Sy…

期权交割对股市是好是坏?2024期权交割日一览表

期权交割是指期权买方在期权合约到期日或之前行使期权&#xff0c;卖方履行义务&#xff0c;按照约定的价格和数量与期权卖方进行标的物的买卖或现金结算的过程。 交割方式 期权交割可以分为实物交割和现金交割&#xff0c;具体取决于合约规定。 实物交割 实物交割是指期权买…

【深度学习基础(1)】什么是深度学习,深度学习与机器学习的区别、深度学习基本原理,深度学习的进展和未来

文章目录 一. 深度学习概念二. 深度学习与机器学习的区别三. 理解深度学习的工作原理1. 每层的转换进行权重参数化2. 怎么衡量神经网络的质量3. 怎么减小损失值 四. 深度学习已取得的进展五. 人工智能的未来 - 不要太过焦虑跟不上 一. 深度学习概念 先放一张图来理解下人工智能…

react中useReducer若有多个值怎么操作?

当 initialState 包含多个对象值时&#xff0c;你的 reducer 函数需要针对每个属性维护其各自的状态更新逻辑。reducer 函数应该返回一个新的状态对象&#xff0c;其中包含了所有必要的属性&#xff0c;保持未更改的属性不变&#xff0c;同时更新那些需要变化的属性。 下面是一…

Vue阶段练习:初始化渲染、获取焦点

阶段练习主要承接Vue 生命周期-CSDN博客 &#xff0c;学习完该部分内容后&#xff0c;进行自我检测&#xff0c;每个练习主要分为效果显示、需求分析、静态代码、完整代码、总结 四个部分&#xff0c;效果显示和准备代码已给出&#xff0c;我们需要完成“完整代码”部分。 练习…

手撕spring框架(1)

相关系列 java中spring底层核心原理解析&#xff08;1&#xff09;-CSDN博客 java中spring底层核心原理解析(2)-CSDN博客 在以上详细讲解了spring底层核心原理&#xff0c;今天我们就来手写一个spring框架。主流程是&#xff1a; 1、编写启动类 2、依据ComponentScan设置的va…

MySQL__三大日志

文章目录 &#x1f60a; 作者&#xff1a;Lion J &#x1f496; 主页&#xff1a; https://blog.csdn.net/weixin_69252724 &#x1f389; 主题&#xff1a;Redis__三大日志 ⏱️ 创作时间&#xff1a;2024年04月30日 ———————————————— 对于MySQL来说, 有…

Gateway Predicate断言(谓词)

是什么 Spring Cloud Gateway匹配路由作为Spring WebFlux HandlerMapping基础设施的一部分。 Spring Cloud Gateway包含许多内置的路由谓词工厂。 所有这些谓词都匹配HTTP请求的不同属性。 您可以使用逻辑 and 语句来联合收割机组合多个路由谓词工厂。 Predicate就是为了实现一…

TiDB系列之:TiDB数据库账号权限,创建TiDB账号,创建数据库,创建表,插入数据

TiDB系列之:TiDB数据库账号权限,创建TiDB账号,创建数据库,创建表,插入数据 一、TiDB账号权限二、创建TiDB账号三、创建数据库,创建表,插入数据一、TiDB账号权限 TiDB账号权限可以分为系统级权限和对象级权限两种,具体如下: 系统级权限: ALL PRIVILEGES:拥有所有权限…