datawhale11月组队学习 模型压缩技术3:2:4结构稀疏化BERT模型

news2024/11/17 4:51:55

文章目录

    • 一、 半结构化稀疏性简介
    • 二、 代码实践
      • 2.1 定义辅助函数
      • 2.2 加载模型、tokenizer和数据集
      • 2.3 测试baseline模型指标
      • 2.4 对BERT-base模型进行半结构稀疏化

  • 《datawhale2411组队学习之模型压缩技术1:模型剪枝(上)》:介绍模型压缩的几种技术;模型剪枝基本概念、分类方式、剪枝标准、剪枝频次、剪枝后微调等内容
  • 《datawhale11月组队学习 模型压缩技术2:PyTorch模型剪枝教程》:介绍PyTorch的prune模块具体用法
  • 《datawhale11月组队学习 模型压缩技术3:2:4结构稀疏化BERT模型》:介绍基于模式的剪枝——2:4结构稀疏化及其在BERT模型上的测试效果

一、 半结构化稀疏性简介

  • 全文参考《Accelerating BERT with semi-structured (2:4) sparsity》
  • Requirements:PyTorch >= 2.1,A NVIDIA GPU with semi-structured sparsity support (Compute Capability 8.0+).

  半结构化稀疏性(Semi-structured Sparsity) 是一种模型优化技术,旨在通过减少神经网络的内存开销和延迟,来加速模型的推理,同时可能会稍微牺牲模型的准确性。它也被称为细粒度结构化稀疏2:4结构化稀疏

  N:M 稀疏度表示 DNN 的稀疏度,即每M个连续权重中固定有N个非零值,其余元素均置为0。一个简单的想法是,避免使用这些参数进行存储/计算。但仅仅将参数归零不会得到显著提升性能,因为密集矩阵乘法内核(dense kernels)仍会计算包含零的元素。

  半结构化稀疏性通过采用稀疏矩阵和压缩存储格式来跳过对零元素的计算,从而提升性能。稀疏矩阵W首先会被压缩,压缩后的矩阵存储着非零的数据值,而metadata则存储着对应非零元素在原矩阵W中的索引信息(非零元素的行号和列号压缩成两个独立的一维数组)如下图所示:

在这里插入图片描述
  NVIDIA在其Ampere架构中引入了对半结构化稀疏性的硬件支持,并发布了快速的稀疏矩阵内核(sparse kernels,例如CUTLASS/cuSPARSELt),使得这种稀疏性模式可以高效加速。另外,与其他稀疏格式相比,半结构化稀疏对模型准确性的影响较小(剪枝后再微调):

NetworkData SetMetricDense FP16Sparse FP16
ResNet-50ImageNetTop-176.176.2
ResNeXt-101_32x8dImageNetTop-179.379.3
XceptionImageNetTop-179.279.2
SSD-RN50COCO2017bbAP24.824.8
MaskRCNN-RN50COCO2017bbAP37.937.9
FairSeq TransformerEN-DE WMT14BLEU28.228.5
BERT-LargeSQuAD v1.1F191.991.9

  从工作流角度看,由于半结构化稀疏性的稀疏度固定为50%,它简化了稀疏化过程,并将其分为两个子问题:一是如何找到最优的2:4稀疏权重以最小化准确性损失;二是如何加速2:4稀疏权重的推理性能并减少内存开销。

  在本教程中,我们将对BERT问答模型进行2:4稀疏化。在微调后,模型准确性损失很小(F1得分86.92 vs 86.48),但推理速度提升了1.3倍。

二、 代码实践

2.1 定义辅助函数

import collections
import datasets
import evaluate
import numpy as np
import torch
import torch.utils.benchmark as benchmark
from torch import nn
from torch.sparse import to_sparse_semi_structured, SparseSemiStructuredTensor
from torch.ao.pruning import WeightNormSparsifier
import transformers

# force CUTLASS use if cuSPARSELt is not available
SparseSemiStructuredTensor._FORCE_CUTLASS = True
torch.manual_seed(100)
def preprocess_validation_function(examples, tokenizer):
    inputs = tokenizer(
        [q.strip() for q in examples["question"]],
        examples["context"],
        max_length=384,
        truncation="only_second",
        return_overflowing_tokens=True,
        return_offsets_mapping=True,
        padding="max_length",
    )
    sample_map = inputs.pop("overflow_to_sample_mapping")
    example_ids = []

    for i in range(len(inputs["input_ids"])):
        sample_idx = sample_map[i]
        example_ids.append(examples["id"][sample_idx])
        sequence_ids = inputs.sequence_ids(i)
        offset = inputs["offset_mapping"][i]
        inputs["offset_mapping"][i] = [
            o if sequence_ids[k] == 1 else None for k, o in enumerate(offset)
        ]

    inputs["example_id"] = example_ids
    return inputs


def preprocess_train_function(examples, tokenizer):
    inputs = tokenizer(
        [q.strip() for q in examples["question"]],
        examples["context"],
        max_length=384,
        truncation="only_second",
        return_offsets_mapping=True,
        padding="max_length",
    )

    offset_mapping = inputs["offset_mapping"]
    answers = examples["answers"]
    start_positions = []
    end_positions = []

    for i, (offset, answer) in enumerate(zip(offset_mapping, answers)):
        start_char = answer["answer_start"][0]
        end_char = start_char + len(answer["text"][0])
        sequence_ids = inputs.sequence_ids(i)

        # Find the start and end of the context
        idx = 0
        while sequence_ids[idx] != 1:
            idx += 1
        context_start = idx
        while sequence_ids[idx] == 1:
            idx += 1
        context_end = idx - 1

        # If the answer is not fully inside the context, label it (0, 0)
        if offset[context_start][0] > end_char or offset[context_end][1] < start_char:
            start_positions.append(0)
            end_positions.append(0)
        else:
            # Otherwise it's the start and end token positions
            idx = context_start
            while idx <= context_end and offset[idx][0] <= start_char:
                idx += 1
            start_positions.append(idx - 1)

            idx = context_end
            while idx >= context_start and offset[idx][1] >= end_char:
                idx -= 1
            end_positions.append(idx + 1)

    inputs["start_positions"] = start_positions
    inputs["end_positions"] = end_positions
    return inputs


def compute_metrics(start_logits, end_logits, features, examples):
    n_best = 20
    max_answer_length = 30
    metric = evaluate.load("squad")

    example_to_features = collections.defaultdict(list)
    for idx, feature in enumerate(features):
        example_to_features[feature["example_id"]].append(idx)

    predicted_answers = []
    # for example in tqdm(examples):
    for example in examples:
        example_id = example["id"]
        context = example["context"]
        answers = []

        # Loop through all features associated with that example
        for feature_index in example_to_features[example_id]:
            start_logit = start_logits[feature_index]
            end_logit = end_logits[feature_index]
            offsets = features[feature_index]["offset_mapping"]

            start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist()
            end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist()
            for start_index in start_indexes:
                for end_index in end_indexes:
                    # Skip answers that are not fully in the context
                    if offsets[start_index] is None or offsets[end_index] is None:
                        continue
                    # Skip answers with a length that is either < 0
                    # or > max_answer_length
                    if (
                        end_index < start_index
                        or end_index - start_index + 1 > max_answer_length
                    ):
                        continue

                    answer = {
                        "text": context[
                            offsets[start_index][0] : offsets[end_index][1]
                        ],
                        "logit_score": start_logit[start_index] + end_logit[end_index],
                    }
                    answers.append(answer)

        # Select the answer with the best score
        if len(answers) > 0:
            best_answer = max(answers, key=lambda x: x["logit_score"])
            predicted_answers.append(
                {"id": example_id, "prediction_text": best_answer["text"]}
            )
        else:
            predicted_answers.append({"id": example_id, "prediction_text": ""})

    theoretical_answers = [
        {"id": ex["id"], "answers": ex["answers"]} for ex in examples
    ]
    return metric.compute(predictions=predicted_answers, references=theoretical_answers)

定义模型基准测试(benchmark)函数:

def measure_execution_time(model, batch_sizes, dataset):
    dataset_for_model = dataset.remove_columns(["example_id", "offset_mapping"])
    dataset_for_model.set_format("torch")
    model.cuda()
    batch_size_to_time_sec = {}
    for batch_size in batch_sizes:
        batch = {
            k: dataset_for_model[k][:batch_size].to(model.device)
            for k in dataset_for_model.column_names
        }

        with torch.inference_mode():
            timer = benchmark.Timer(
                stmt="model(**batch)", globals={"model": model, "batch": batch}
            )
            p50 = timer.blocked_autorange().median * 1000
        batch_size_to_time_sec[batch_size] = p50
    return batch_size_to_time_sec

2.2 加载模型、tokenizer和数据集

# load model
model_name = "bert-base-cased"
tokenizer = transformers.AutoTokenizer.from_pretrained(model_name)
model = transformers.AutoModelForQuestionAnswering.from_pretrained(model_name)
print(f"Loading tokenizer: {model_name}")
print(f"Loading model: {model_name}")

# set up train and val dataset
squad_dataset = datasets.load_dataset("squad")
tokenized_squad_dataset = {}
tokenized_squad_dataset["train"] = squad_dataset["train"].map(
    lambda x: preprocess_train_function(x, tokenizer), batched=True
)
tokenized_squad_dataset["validation"] = squad_dataset["validation"].map(
    lambda x: preprocess_validation_function(x, tokenizer),
    batched=True,
    remove_columns=squad_dataset["train"].column_names,
)
data_collator = transformers.DataCollatorWithPadding(tokenizer=tokenizer)

2.3 测试baseline模型指标

  接下来,我们在SQuAD数据集上训练一个快速的基线模型。SQuAD任务要求模型在给定的上下文(如维基百科文章)中识别出回答问题的文本片段。运行以下代码,我们得到了86.9的F1得分,这个结果与NVIDIA报告的得分非常接近。差异可能来源于使用的是BERT-base而不是BERT-large,或者是微调的超参数不同。

training_args = transformers.TrainingArguments(
    "trainer",
    num_train_epochs=1,
    lr_scheduler_type="constant",
    per_device_train_batch_size=64,
    per_device_eval_batch_size=512,
)

trainer = transformers.Trainer(
    model,
    training_args,
    train_dataset=tokenized_squad_dataset["train"],
    eval_dataset=tokenized_squad_dataset["validation"],
    data_collator=data_collator,
    tokenizer=tokenizer,
)

trainer.train()

# batch sizes to compare for eval
batch_sizes = [4, 16, 64, 256]
# 2:4 sparsity require fp16, so we cast here for a fair comparison
with torch.autocast("cuda"):
    with torch.inference_mode():
        predictions = trainer.predict(tokenized_squad_dataset["validation"])
    start_logits, end_logits = predictions.predictions
    fp16_baseline = compute_metrics(
        start_logits,
        end_logits,
        tokenized_squad_dataset["validation"],
        squad_dataset["validation"],
    )
    fp16_time = measure_execution_time(
        model,
        batch_sizes,
        tokenized_squad_dataset["validation"],
    )
print("fp16", fp16_baseline)
print("cuda_fp16 time", fp16_time)
fp16 {'exact_match': 78.53358561967833, 'f1': 86.9280493093186}
cuda_fp16 time {4: 10.927572380751371, 16: 19.607915310189128, 64: 73.18846387788653, 256: 286.91255673766136}

2.4 对BERT-base模型进行半结构稀疏化

  剪枝策略有很多,我们通过幅度剪枝(magnitude pruning)来修剪BERT模型。幅度剪枝的目标是移除L1范数最小的权重,这也是NVIDIA在所有实验中使用的剪枝策略

  我们将使用torch.ao.pruning包中的权重范数稀疏化器( weight-norm sparsifier)来实现这一过程。该稀疏化器通过对模型的权重张量应用掩码(mask)来执行剪枝操作。整个剪枝流程如下:

  1. 选择剪枝层:首先选择哪些层应用剪枝。在这个案例中,我们对所有的nn.Linear层进行剪枝,除了任务特定的头部输出层,因为后者的形状约束不适用于半结构化稀疏性。

  2. 插入参数化:通过torch.ao.pruning包中的工具,插入一个mask参数化来标记每个权重。在剪枝前,权重矩阵被标记为mask * weight,通过掩码来控制哪些权重被剪除。

  3. 进行剪枝:调用pruner.update_mask()方法,更新模型中指定层的权重掩码。update_mask()方法会根据剪枝策略(如幅度剪枝)更新每个权重的掩码,将最小的L1范数的权重设置为零。

  4. 评估剪枝后的模型:在不进行微调的情况下,评估剪枝后的模型的性能,以查看zero-shot剪枝(即未进行微调)的准确性损失。这个步骤主要是为了观察剪枝后的模型精度的下降情况。

  5. 微调模型:对剪枝后的模型进行微调,以恢复剪枝导致的精度损失。在这个过程中,未被剪枝的权重会得到更新,从而提高整体模型的准确性。

  6. 剪枝永久化:一旦微调完成并且模型的性能恢复到满意水平,可以调用squash_mask()方法。该方法将掩码与权重融合,移除所有参数化操作,留下最终的剪枝模型。

  7. 加速推理:使用已经剪枝并融合掩码的模型进行加速推理。此时,模型不再需要计算零权重,推理速度得以提高。在某些情况下,尤其是在批量较小的情况下,稀疏内核可能比密集内核慢,但在适当的条件下可以带来加速效果。

# 1. 选择剪枝层和剪枝策略
sparsifier = WeightNormSparsifier(
    # apply sparsity to all blocks
    sparsity_level=1.0,
    # shape of 4 elemens is a block
    sparse_block_shape=(1, 4),
    # two zeros for every block of 4
    zeros_per_block=2
)

# add to config if nn.Linear and in the BERT model.
sparse_config = [
    {"tensor_fqn": f"{fqn}.weight"}
    for fqn, module in model.named_modules()
    if isinstance(module, nn.Linear) and "layer" in fqn
]
# 2. 修剪模型的第一步是插入参数化以屏蔽模型的权重,这是通过prepare step完成的。
# 每当我们试图访问.weight时,我们都会得到mask*weight。
sparsifier.prepare(model, sparse_config)
print(model.bert.encoder.layer[0].output)
BertOutput(
  (dense): ParametrizedLinear(
    in_features=3072, out_features=768, bias=True
    (parametrizations): ModuleDict(
      (weight): ParametrizationList(
        (0-5): 6 x FakeSparsity()
      )
    )
  )
  (LayerNorm): LayerNorm((768,), eps=1e-12, elementwise_affine=True)
  (dropout): Dropout(p=0.1, inplace=False)
)
# 3. 进行剪枝
sparsifier.step()
# 4. 评估剪枝后的模型性能
with torch.autocast("cuda"):
    with torch.inference_mode():
        predictions = trainer.predict(tokenized_squad_dataset["validation"])
    pruned = compute_metrics(
        *predictions.predictions,
        tokenized_squad_dataset["validation"],
        squad_dataset["validation"],
    )
print("pruned eval metrics:", pruned)
pruned eval metrics: {'exact_match': 40.59602649006622, 'f1': 56.51610004515979}
# 5. 微调模型
trainer.train()
# 6. 剪枝永久化
sparsifier.squash_mask()
torch.set_printoptions(edgeitems=4)
print(model.bert.encoder.layer[0].intermediate.dense.weight)
Parameter containing:
tensor([[ 0.0000, -0.0237,  0.0000,  0.0130,  ..., -0.0462, -0.0000, 0.0000, -0.0272],
       [ 0.0436, -0.0000, -0.0000,  0.0492,  ..., -0.0000,  0.0844,  0.0340, -0.0000],
       [-0.0302, -0.0350,  0.0000,  0.0000,  ...,  0.0303,  0.0175, -0.0000,  0.0000],
       [ 0.0000, -0.0000, -0.0529,  0.0327,  ...,  0.0213,  0.0000, -0.0000,  0.0735],
       ...,
       [ 0.0000, -0.0000, -0.0258, -0.0239,  ..., -0.0000, -0.0000,  0.0380,  0.0562],
       [-0.0432, -0.0000,  0.0000, -0.0598,  ...,  0.0000, -0.0000,  0.0262  -0.0227],
       [ 0.0244,  0.0921, -0.0000, -0.0000,  ..., -0.0000, -0.0784,  0.0000,  0.0761],
       [ 0.0000,  0.0225, -0.0395, -0.0000,  ..., -0.0000,  0.0684, -0.0344, -0.0000]], device='cuda:0', requires_grad=True)
# 7. 评估微调后的剪枝模型
model = model.cuda().half()
# accelerate for sparsity
for fqn, module in model.named_modules():
    if isinstance(module, nn.Linear) and "layer" in fqn:
        module.weight = nn.Parameter(to_sparse_semi_structured(module.weight))

with torch.inference_mode():
    predictions = trainer.predict(tokenized_squad_dataset["validation"])
start_logits, end_logits = predictions.predictions
metrics_sparse = compute_metrics(
    start_logits,
    end_logits,
    tokenized_squad_dataset["validation"],
    squad_dataset["validation"],
)
print("sparse eval metrics: ", metrics_sparse)
sparse_perf = measure_execution_time(
    model,
    batch_sizes,
    tokenized_squad_dataset["validation"],
)
print("sparse perf metrics: ", sparse_perf)
sparse eval metrics:  {'exact_match': 78.43897824030275, 'f1': 86.48718950090766}
sparse perf metrics:  {4: 12.621004460379481, 16: 15.368514601141214, 64: 58.702805917710066, 256: 244.19364519417286}

下面是进一步的测试结果:

Metricfp162:4 sparseDelta / Speedup
Exact Match (%)78.5378.44-0.09
F1 (%)86.9386.49-0.44
Time (bs=4)10.9312.620.87x
Time (bs=16)19.6115.371.28x
Time (bs=64)73.1958.701.25x
Time (bs=256)286.91244.191.18x

  可见,在剪枝后进行微调,模型恢复了几乎所有因剪枝造成的F1得分损失,这意味着微调后的剪枝模型并没有显著的精度损失。另外,在batch_size较小的情况下,稀疏内核的计算可能不如密集内核高效,无法带来加速效果。本次测试中,在batch_size=16时,加速效果最好,加速比为1.28x

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

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

相关文章

大数据学习15之Scala集合与泛型

1. 概述 大部分编程语言都提供了数据结构对应的编程库&#xff0c;并称之为集合库(Collection Library)&#xff0c;Scala 也不例外&#xff0c;且它还拥有以下优点&#xff1a; 易用&#xff1a;灵活组合运用集合库提供的方法&#xff0c;可以解决大部分集合问题 简洁&#xf…

force stop和pm clear的区别

前言&#xff1a;因为工作中遇到force stop和pm clear进程后&#xff0c;进程不能再次挂起&#xff0c;谷歌系统共性问题&#xff0c;服务类应用经清缓存后当下服务就会挂掉&#xff0c;需要系统重启才能恢复。为了更好的“丢锅”&#xff0c;需要进一步学习force stop和pm cle…

SAP+Internet主题HTML样式选择

SAP目前只支持三种HTML样式选择&#xff1a; 样式一 背景色&#xff1a;深色&#xff0c;蓝 特点&#xff1a;适中型排列&#xff0c;与SAP界面排列相同&#xff0c;富含UI特征&#xff0c;整齐美观 URL地址&#xff1a;http://cn1000-sap-01.sc.com:8000/sap/bc/gui/sap/it…

VBA学习笔记:点击单元格显示指定的列

应用场景&#xff1a; 表格中列数较多&#xff0c;特定条件下隐藏一些无关的列&#xff0c;只保留相关的列&#xff0c;使表格更加清晰。 示例&#xff1a;原表格如下 点击一年级&#xff0c;只显示一年级相关的科目&#xff1a; 点击二年级&#xff0c;只显示二年级相关的科…

java版嘎嘎快充汽车单车充电系统源码系统jeecgboot

汽车使用云快充1.6 1.5协议&#xff0c;单车用的铁塔协议 前端uniapp、后端jeecgbootvue2

这些场景不适合用Selenium自动化!看看你踩过哪些坑?

Selenium是自动化测试中的一大主力工具&#xff0c;其强大的网页UI自动化能力&#xff0c;让测试人员可以轻松模拟用户操作并验证系统行为。然而&#xff0c;Selenium并非万能&#xff0c;尤其是在某些特定场景下&#xff0c;可能并不适合用来自动化测试。本文将介绍Selenium不…

qt QFrame详解

1、概述 QFrame是Qt框架中用于提供框架或边框的控件&#xff0c;主要用于在图形用户界面&#xff08;GUI&#xff09;中创建框架&#xff0c;并提供各种边框样式和功能。它是Qt中一个基础的容器类&#xff0c;也是许多基础控件的基类&#xff0c;可以被QLCDNumber、QToolBox、…

python json详解

json 是 Python 中用于处理 JSON 数据的标准库。JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人类阅读和编写&#xff0c;同时也易于机器解析和生成。Python 的 json 模块提供了将 Python 对象与 JSON 数据相互转换的功…

ISUP协议视频平台EasyCVR私有化部署视频平台如何实现RTMP推流将大疆无人机的视频画面回传?

在现代视频监控和流媒体技术领域&#xff0c;EasyCVR视频融合云平台以其卓越的性能和灵活性&#xff0c;成为了跨区域、网络化视频监控综合管理的理想选择。作为TSINGSEE青犀视频“云边端”架构体系中的核心组件&#xff0c;私有化部署视频平台EasyCVR不仅能够实现视频数据的集…

【时间之外】IT人求职和创业应知【37】-AIGC私有化

目录 新闻一&#xff1a;2024智媒体50人成都会议暨每经20周年财经媒体峰会召开 新闻二&#xff1a;全球机器学习技术大会在北京召开 新闻三&#xff1a;区块链技术在金融领域的应用取得新突破 不知不觉的坚持了1个月&#xff0c;按照心理学概念&#xff0c;还要坚持2个月&am…

SqlServer 存储临时数据

WITH 子句中的 公用表表达式&#xff08;CTE&#xff09;、临时表&#xff08;Temporary Table&#xff09; 和 表变量&#xff08;Table Variable&#xff09; 都可以存储临时数据&#xff0c;但它们的使用场景、生命周期、作用范围和性能特性有所不同。下面是这三者之间的主要…

jmeter基础05_第1个http请求

本节课使用网站“httpbin.org”进行基础的http请求全流程。 请求获取httpbin.org的首页&#xff1a; 请求方法&#xff1a;GET URL&#xff1a;http://httpbin.org 参数&#xff1a;无 1、操作步骤 ① 打开jmeter&#xff1a;命令行窗口输入“jmeter”并回车。 ② 添加线程组…

SpringBoot+MyBatis+MySQL的Point实现范围查找

前言 最近做了一个功能&#xff0c;需要通过用户当前位置点获取指定范围内的数据。由于后端存储用的是 MySQL&#xff0c;故选择使用 MySQL 中的 Point 实现范围查找功能。ORM 框架用的是 MyBatis&#xff0c;MyBatis 原生并不支持 Point 字段与 POJO 的映射&#xff0c;需要自…

计算机网络中的数据包传输机制详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机网络中的数据包传输机制详解 计算机网络中的数据包传输机制详解 计算机网络中的数据包传输机制详解 引言 数据包的基本概念…

DAY110代码审计-PHP框架开发篇ThinkPHP版本缺陷不安全写法路由访问利用链

https://blog.csdn.net/m0_60571842/article/details/139057898 看这个原作者 知识点&#xff1a; 1、PHP框架学习-ThinkPHP-架构&调试&路由&接受2、PHP框架审计-ThinkPHP-不安全写法&版本漏洞 框架审计总结方向&#xff1a; 1、版本不安全写法怎么检测 -本…

【日志】力扣11.盛水最多的容器

2024.11.15 【力扣刷题】 11.盛水最多的容器 - 力扣&#xff08;LeetCode&#xff09; int maxArea(int* height, int heightSize) {int max 0;int V 0;int left 0;int right heightSize - 1;while (left < right) {if (height[left] > height[right]) {V height[r…

leetcode100:相同的树

给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例 1&#xff1a; 输入&#xff1a;p [1,2,3], q [1,2,3] 输出&#xff1a;true示例 2&…

❤React-React 组件基础(类组件)

❤React-React 组件基础 1、组件化开发介绍 组件化开发思想&#xff1a;分而治之 React的组件按照不同的方式可以分成类组件&#xff1a; 划分方式一&#xff08;按照组件的定义方式&#xff09; 函数组件(Functional Component )和类组件(Class Component)&#xff1b; …

SQL面试题——抖音SQL面试题 主播播出时长

主播播出时长 现有如下数据,主播id、房间号、播出的批次号,每个批次号进出房间的时间戳、分区时间: 每一次直播都有一个上播和下播,每个房间里,同一个批次号会有两条数据,分别记录了上播和下播时间,求每个主播的播出时长? 通过上面的数据,可以清晰的看出,同一个批次…

无人机检测车辆——多目标检测

目录 YOLOv3&#xff08;You Only Look Once version 3&#xff09;简介 YOLOv3 的主要特点 YOLOv3 的结构 1. 特征提取网络&#xff08;Backbone&#xff09; 2. 检测头&#xff08;Head&#xff09; 3. 输出层 YOLOv3 损失函数 YOLOv3 的优势 YOLOv3 的应用 YOLOv3…