PyTorch 微调终极指南:第 1 部分 — 预训练模型及其配置

news2024/11/18 0:34:56

一、说明

        如今,在训练深度学习模型时,通过在自己的数据上微调预训练模型来迁移学习已成为首选方法。通过微调这些模型,我们可以利用他们的专业知识并使其适应我们的特定任务,从而节省宝贵的时间和计算资源。本文分为四个部分,侧重于微调模型的不同方面。

第一部分概述

  • 简介 — 模型及其配置
  • 加载预训练模型
  • 修改模型头
  • 设置学习率、优化器和权重衰减
  • 选择损失函数
  • 冻结全部或部分网络
  • 定义模型浮点精度
  • 训练和验证模式
  • 单显卡和多显卡
  • 结论

二、介绍

        定义模型包括一系列重要决策,包括选择合适的架构、自定义模型头、配置损失函数和学习率、设置所需的浮点精度以及确定要冻结或微调的层等等。在本文中,我们将详细探讨这些方面,提供有价值的见解来帮助您有效地定义和微调模型。

2.1 加载预训练模型

DensNet 架构示例

在加载预先训练的模型之前,清楚地了解您的特定问题并相应地选择合适的架构至关重要。虽然此任务可能看起来具有挑战性,但重要的是不要随机选择模型体系结构。考虑您的业务要求,并选择符合这些需求的合适架构。

例如,如果您要对分类进行微调,并且低延迟是优先事项,那么像MobileNet这样的架构将是一个不错的选择。通过做出明智的架构决策,您可以优化微调实验以获得更好的结果。

请注意 — 您可以从多个来源加载预先训练的模型以进行微调。在本文中,我指的是timm(Pytorch图像模型)和Torchvision模型

下面是一个从火炬视觉加载预先训练的resnet50模型的示例:

# From torchvision.models
from torchvision import models
model = models.resnet50(pretrained=False)

从 timm 加载(Pytorch 图像模型):

import timm

# from timm
pretrained_model_name = "resnet50"
model = timm.create_model(pretrained_model_name, pretrained=False)

需要注意的是,无论预训练模型的来源如何,所需的关键修改都是调整模型的全连接 FC 层(或者可以是线性/分类器/头部)。此外,对于您的目标任务,您可以合并额外的线性图层。我们将在下一节中进一步探讨这一点。

2.2 修改模型头

对适用于目标任务的预训练模型的视觉描述,具有除头部修改之外的其他层。来源: OpenCV

        修改模型的头部对于使其与您的特定目标任务保持一致至关重要。预训练模型在大型数据集(如ImageNet)上进行图像分类,或在文本数据(如BooksCorpus和Wikipedia)上进行文本生成。通过修改模型的头部,预先训练的模型可以适应新任务并利用它所学到的有价值的特征,从而提高其在新任务中的性能。

        例如,您可以修改分类任务的 RestNet 头:

import torch.nn as nn
import timm

num_classes = 4 # Replace num_classes with the number of classes in your data

# Load pre-trained model from timm
model = timm.create_model('resnet50', pretrained=True)

# Modify the model head for fine-tuning
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, num_classes)

        或者,您可以修改分类任务的 RestNet 头,同时添加额外的线性层以增强模型的预测能力(ps — 这只是一个说明性示例):

import torch.nn as nn
import timm

num_classes = 4 # Replace num_classes with the number of classes in your data

# Load pre-trained model from timm
model = timm.create_model('resnet50', pretrained=True)

# Modify the model head for fine-tuning
num_features = model.fc.in_features

# Additional linear layer and dropout layer
model.fc = nn.Sequential(
    nn.Linear(num_features, 256),  # Additional linear layer with 256 output features
    nn.ReLU(inplace=True),         # Activation function (you can choose other activation functions too)
    nn.Dropout(0.5),               # Dropout layer with 50% probability
    nn.Linear(256, num_classes)    # Final prediction fc layer
)

        或者你可以修改 RestNet 头的回归任务:

model = timm.create_model('resnet50', pretrained=True)

# Modify the model head for regression
num_features = model.fc.in_features
model.fc = nn.Linear(num_features, 1) # Regression task has a single output

        需要注意的一件事 — 模型并不总是有一个我们修改输出特征(例如,num_classes)的 FC(全连接)层。模型的架构可能会有所不同,我们需要修改的层的名称和位置可能会有所不同。

在许多预训练模型中,特别是在卷积神经网络 (CNN) 架构中,模型末尾通常有一个线性层或 FC 层来执行最终分类。但是,这不是一个严格的规则,某些模型可能具有不同的结构。

要识别需要修改的图层,您可以执行以下操作,例如:

import torch
import timm

# Load pre-trained model from timm
model = timm.create_model('resnet50', pretrained=True)
print(model)

打印的模型架构显示需要修改的特定最后一层的标识。来源: kakaocdn.net

通过打印模型,您可以查看其体系结构并确定要修改的相应层。查找用作最终分类图层的线性或 FC 图层,并将其替换为与类数或任务要求匹配的新图层。

三、设置优化器、学习率、权重衰减和动量

Setting Optimizer, Learning Rate, Weight Decay, and Momentum

Image source

        在微调中,学习率、损失函数和优化器是相互关联的组件,它们共同影响模型适应新任务的能力,同时利用从预训练中获得的知识。精心选择的学习率可确保以合理的速度有效收敛模型,精心选择的损失函数将训练过程中的损失最小化与目标任务对齐,适当的优化器可以有效地优化模型的参数。

        微调需要对这些组件进行仔细的实验和迭代调整,以在微调模型中取得适当的平衡并达到所需的性能水平。

3.1 优化器

为了更详细地了解如何选择正确的优化器进行微调,我建议参考博客 https://towardsdatascience.com/7-tips-to-choose-the-best-optimizer-47bb9c1219e

优化器根据反向传播期间计算的梯度确定用于更新模型参数的算法。不同的优化器(如 SGD、Adam 或 RMSprop)具有不同的参数更新规则和收敛属性。优化器的选择会显著影响模型训练和微调模型的最终性能。选择最合适的优化器需要考虑任务的性质、数据集的大小和可用的计算资源等因素。

3.2 学习率、动量和权重衰减

在定义优化器时,我们还必须设置学习率(LR),这是一个超参数,用于确定优化期间每次迭代的步长。它控制在反向传播期间响应计算的梯度而更新的模型参数的量。选择合适的学习率至关重要,因为设置得太高可能会导致优化过程振荡或发散或超过最佳解,而设置得太低可能会导致收敛缓慢或陷入局部最小值。

除了学习率之外,在定义优化器时还需要考虑其他关键的超参数,例如权重衰减和动量(特定于 SGD)。让我们快速浏览一下这两个超参数:

  • 权重衰减,也称为 L2 正则化,是一种用于防止过度拟合并鼓励模型学习更简单、更可泛化表示的技术。
  • 动量用于随机梯度下降(SGD),以加速收敛并逃脱局部最小值。
import torch.optim as optim

# Define your optimizer with weight decay
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=0.001)

# Define your optimizer with weight decay
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=0.0001)

四、选择损失函数

图片来源 : naptune.ai

要为您的目标任务探索和选择合适的损失函数,我建议参考有关损失函数的官方 PyTorch 文档。

        损失函数测量模型的预测输出与实际正确答案之间的差异或差距。它为我们提供了一种了解模型在任务上的表现的方法。在微调预训练模型时,选择适合我们正在处理的特定任务的损失函数非常重要。例如,对于分类任务,通常使用交叉熵损失,而均方误差更适合回归问题。选择正确的损失函数可确保模型专注于在训练期间优化所需目标。

import torch.nn as nn

# Define the loss function - For classification problem
loss_function = nn.CrossEntropyLoss()

# Define the loss function - For regression problem
loss_function = nn.MSELoss()  # Mean Squared Error loss

        还要注意的是,在选择和处理损失函数方面,还可以应用一些额外的考虑因素和技术。这方面的一些例子是:

  • 自定义损失函数 — 您可能需要修改或自定义损失函数以满足特定要求。一个例子是对重要的单个类别的错误分类进行 10 倍的惩罚。下面是一个示例代码,演示了自定义损失的实现,为您提供了如何完成它的想法:
import torch
import torch.nn.functional as F

class CustomLoss(torch.nn.Module):
    def __init__(self, class_weights):
        super(CustomLoss, self).__init__()
        self.class_weights = class_weights

    def forward(self, inputs, targets):
        ce_loss = F.cross_entropy(inputs, targets, reduction='none')
        weights = torch.ones_like(targets).float()
        for class_idx, weight in enumerate(self.class_weights):
            weights[targets == class_idx] = weight
        weighted_loss = ce_loss * weights
        return torch.mean(weighted_loss)

# Assuming you have a model and training data
model = YourModel()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# Assuming 5 classes so class_weights = [1.0, 1.0, 1.0, 1.0, 10.0]
criterion = CustomLoss(class_weights=[1.0, 1.0, 1.0, 1.0, 10.0])  

# Inside the training loop
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
  • 基于指标的损失 — 在某些情况下,可能会根据损失本身以外的指标来评估模型的性能。在这种情况下,您可以设计或调整损失函数以直接针对这些指标进行优化。
  • 正则化 — 正则化方法(例如 L1 或 L2 正则化)可以在微调期间合并到损失函数中,以防止过度拟合并改善模型泛化。正则化术语可以帮助控制模型的复杂性,并降低过分强调数据中特定模式或特征的风险。L2 正则化可以通过在优化器中设置值来应用,而 L1 正则化需要的方法略有不同。weight_decay

L2 正则化实现:

# Define a loss function
criterion = nn.CrossEntropyLoss()

# L2 regularization 
optimizer = optim.SGD(model.parameters(), lr=0.01, weight_decay=0.01)

L1 正则化实现:

# Define a loss function
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# Inside the training loop
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, targets)

# L1 regularization
regularization_loss = 0.0
for param in model.parameters():
    regularization_loss += torch.norm(param, 1)
loss += 0.01 * regularization_loss#Adjust regularization strength as needed

还有一件事 😁

来源 : tenor.com

五、 冻结全部或部分网络

图片来源 : www.therescuevets.com

当我们提到冻结时,这意味着在微调过程中固定特定层或整个网络的权重。Netwrok 冻结允许我们保留预训练模型捕获的知识,同时只更新某些层以适应目标任务。因此,这非常重要,如果您正在微调预先训练的模型,则不应忽略这一点。

在微调之前,决定是应该冻结预训练模型的所有层(全网络)还是部分层,归结为你特定的目标任务。

例如,如果预训练模型已在类似于目标任务的大规模数据集上进行了训练,则冻结整个网络可以帮助保留学习的表示,防止它们被覆盖。在这种情况下,只有模型的头部被修改和从头开始训练。

另一方面,当预训练模型的较低层捕获可能与新任务相关的一般特征时,仅冻结网络的一部分可能很有用。通过冻结这些较低层,我们可以利用预先训练的模型的知识,同时更新较高层以专注于特定于任务的功能。此方法在目标数据集较小或与训练预训练模型的数据集明显不同的情况下特别有用。

要在 PyTorch 中实现冻结,您可以访问模型中的各个层或模块,并将其属性设置为 。这样可以防止在向后传递期间计算梯度和更新权重。requires_gradFalse

下面是一个示例代码,演示了冻结整个网络的实现:

# Freeze all the layers of the pre-trained model
for param in model.parameters():
    param.requires_grad = False

# Modify the model's head for a new task
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)

仅冻结网络中的卷积层

# Freeze only the convolutional layers of the pre-trained model
for param in model.parameters():
    if isinstance(param, nn.Conv2d):
        param.requires_grad = False

# Modify the model's head for a new task
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)

仅冻结网络中的特定层

# Freeze specific layers (e.g.,the first two convolutional layers) of the pre-trained model
for name, param in model.named_parameters():
    if 'conv1' in name or 'layer1' in name:
        param.requires_grad = False

# Modify the model's head for a new task
num_classes = 10
model.fc = nn.Linear(model.fc.in_features, num_classes)

请务必注意,考虑到任务和数据集的特定要求,应深思熟虑地进行冻结图层。这是利用预先训练的知识与允许模型有效适应新任务之间的微妙平衡。

六、定义模型浮点精度

了解混合精度训练 乔纳森·戴维斯

        快速汇总定义模型浮点精度是指深度学习模型中用于表示数值的数据类型。在 PyTroch 中,32 位(float32 或 FP32)和 16 位(float16 或 FP16 或半精度)是两种常用的浮点精度。

  • float32 — 此精度提供宽动态范围和高数值精度,允许精确计算,但消耗更多内存。FP32 使用 32 位来表示一个数字。
  • float16 — 这种较低的精度可以减少模型的内存占用和计算要求,从而可能提高效率和速度。但是,这可能会导致数值精度损失,并可能影响模型的准确性或收敛性。FP16 使用 16 位来表示一个数字。

        FP16 和 FP32 被称为单精度,正如我们在上面的指针中看到的那样,两者都有自己的优点和缺点。为了利用两者的优势,我们有混合精度,它在训练管道中结合了 FP16 和 FP32 浮点精度。混合精度可提高计算效率、减少内存占用、加速训练并增加模型容量。

要更详细地了解混合精度训练,我建议参考文章“了解混合精度训练”

        下面是如何使用自动混合精度 (AMP) 库在 PyTorch 训练管道中实现混合精度训练的示例:

import torch
from torch import nn, optim
from torch.cuda.amp import autocast, GradScaler

# Define your model and optimizer
model = YourModel()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# Define loss function
criterion = nn.CrossEntropyLoss()

# Define scaler for automatic scaling of gradients
scaler = GradScaler()

# Define your training loop
for epoch in range(num_epochs):
    for batch_idx, (data, targets) in enumerate(train_loader):
        data, targets = data.to(device), targets.to(device)
        
        # Zero the gradients
        optimizer.zero_grad()
        
        # Enable autocasting for mixed precision
        with autocast():
            # Forward pass
            outputs = model(data)
            loss = criterion(outputs, targets)
        
        # Perform backward pass and gradient scaling
        scaler.scale(loss).backward()
        
        # Update model parameters
        scaler.step(optimizer)
        scaler.update()
        
        # Print training progress
        if batch_idx % log_interval == 0:
            print(f"Epoch {epoch+1}/{num_epochs} | Batch {batch_idx}/{len(train_loader)} | Loss: {loss.item():.4f}")

在上面的示例中,对象用于执行渐变缩放。以下是所用方法的细分:GradScaler()

  1. scaler.scale(loss):此方法按缩放器确定的适当因子缩放损失值。它返回将用于向后传递的缩放损失。
  2. scaler.step(optimizer):此方法使用向后传递期间计算的梯度更新优化器的参数。它像往常一样执行优化器步骤,但考虑了缩放器执行的梯度缩放。
  3. scaler.update():此方法调整缩放器在下一次迭代中使用的比例因子。它通过根据梯度大小动态调整刻度来帮助防止下溢或溢出问题。它被称为 .scaler.step(optimizer)

使用和相关方法的目的是减轻使用低精度 (FP16) 计算时可能出现的潜在数值不稳定问题。通过适当地缩放损失和梯度,缩放器可确保优化器的更新保持在稳定的范围内。GradScaler()

这种使用 PyTorch 的 AMP 库实现混合精度训练的做法允许有效利用 FP16 计算,以提高训练速度并减少内存使用,同时保持使用 FP32 进行准确权重更新所需的精度。

虽然混合精度训练可以带来一些好处,但在某些情况下,它可能不适合或可能损害训练过程。

使用混合精度训练的潜在危害包括:

  • 由于 FP16 导致的数值精度损失和这种精度损失可能会导致模型精度降低,尤其是在需要高精度的任务中。当涉及到数值精度关键型任务时,混合精度可能不适用。
  • 由于训练期间的数值不稳定,下溢和溢出的脆弱性增加,这可能会影响模型的收敛性和性能。
  • 由于混合精度而增加复杂性,这需要额外的考虑因素,例如管理精度转换、缩放梯度以及处理与精度不匹配相关的可能问题。
  • 如果您的模型遇到严重的梯度爆炸或消失问题,则在混合精度训练中切换到低精度计算 (FP16) 可能会使这些问题恶化。 在这种情况下,在考虑混合精度训练之前,解决潜在的不稳定问题至关重要。

七、训练和验证模式

用于训练与验证(推理)模式的图像参考。图片来源 : teledyneimaging.com

微调模型时,在加载预训练模型后,它最初默认处于训练模式。但是,我们可以在推理或验证期间将模型切换到验证模式。这些模式更改会相应地改变模型的行为。

7.1 训练模式

当模型处于训练模式时,它会启用训练过程中所需的特定操作,例如计算梯度、更新参数和应用正则化技术(如 dropout)。在此模式下,模型的行为就像是在训练数据集上进行训练一样,并准备好从数据中学习。

model.train() # sets model in training mode

7.2 验证模式

        当模型处于评估模式时,它会禁用某些仅在训练期间需要的操作,例如计算梯度、dropout 和更新参数。此模式通常在验证或测试期间使用,当您想要评估模型对未见过的数据的性能时。

model.val() # sets model in validation mode

        在微调期间将模型设置为正确的模式非常重要,因为将模型设置为正确的模式可确保每个阶段(训练或评估)的行为一致和正确的操作。这样可以获得准确的结果、高效的资源使用,并防止过度拟合或规范化不一致等问题。

7.3 单显卡和多显卡

具有多个 GPU 的计算机。图片来源:普吉特系统

        GPU 对于深度学习和微调任务至关重要,因为它们擅长执行高度并行的计算,从而显着加快训练过程。在可以访问多个 GPU 的情况下,您可以利用它们的集体力量来进一步加速训练。下面是如何利用多个 GPU 的示例(如果有):

# Define your model
model = MyModel()
model = model.to(device)  # Move the model to the desired device (CPU or GPU)

# Check if multiple GPUs are available
if torch.cuda.device_count() > 1:
    print("Using", torch.cuda.device_count(), "GPUs for training.")
    model = nn.DataParallel(model)  # Wrap the model with DataParallel

# Define your loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

此代码段首先使用 检查是否有多个 GPU 可用。如果有多个 GPU,则模型将包装为 ,允许它利用所有可用的 GPU 进行训练。每个 GPU 同时处理一部分数据,从而缩短训练时间。如果仅存在单个 GPU,则代码将在该 GPU 上运行,而不使用 .torch.cuda.device_count()nn.DataParallelnn.DataParallel

八、结论

        在 PyTorch 微调终极指南的第一部分中,我们探讨了微调预训练模型以适应我们的特定任务所涉及的基本步骤。通过利用迁移学习,我们可以节省大量时间和计算资源,同时取得令人印象深刻的结果。在本文中,我们学习了如何加载预先训练的模型,调整其头部架构以匹配目标任务,以及自定义超参数(如学习率、优化器和权重衰减)以优化微调过程。此外,我们还研究了选择合适的损失函数以及冻结网络特定部分以实现更可控的微调的好处。

        此外,我们还讨论了定义模型浮点精度的重要性,以及在微调期间在训练和验证模式之间切换的重要性。此外,我们还探讨了如何充分利用单 GPU 和多 GPU 设置来加速训练并提高性能。

        在本系列的第 2 部分中,我们将深入研究高级技术,以提高微调模型的准确性和泛化能力。我们将探索数据增强、学习率时间表、梯度裁剪和集成等方法,以进一步提高模型在多样化和具有挑战性的数据集上的性能。

Ultimate Guide to Fine-Tuning in PyTorch : Part 1 — Pre-trained Model and Its Configuration | by Ruman Khan | Jul, 2023 | Medium

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

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

相关文章

还不知道Java类加载机制,你算白学了

1 前言 在Java的世界里,每一个类或者接口,在经历编译器后,都会生成一个个.class文件。类加载机制指的是将这些.class文件中的二进制数据读入到内存中,并对数据进行校验,解析和初始化。最终,每一个类都会在…

Not All Features Matter:Enhancing Few-shot CLIP with Adaptive Prior Refinement

APE是ICCV2023的一篇文章,也是我在这个领域里接触的第一篇文章,这里主要做一下记录。 论文链接:2304.01195.pdf (arxiv.org) 代码链接:yangyangyang127/APE: [ICCV 2023] Code for "Not All Features Matter: Enhancing Fe…

【深度学习注意力机制系列】—— SKNet注意力机制(附pytorch实现)

SKNet(Selective Kernel Network)是一种用于图像分类和目标检测任务的深度神经网络架构,其核心创新是引入了选择性的多尺度卷积核(Selective Kernel)以及一种新颖的注意力机制,从而在不增加网络复杂性的情况…

立即开始使用 3D 图像

一、说明 这个故事介绍了使用这种类型的数据来训练机器学习3D模型。特别是,我们讨论了Kaggle中可用的MNIST数据集的3D版本,以及如何使用Keras训练模型识别3D数字。 3D 数据无处不在。由于我们希望构建AI来与我们的物理世界进行交互,因此使用3…

安装LED透明屏需要注意这4点

随着LED显示屏的广泛应用,各种大屏幕随处可见。透明LED显示屏的安装方法多种多样,涵盖了屋顶式、立柱式、挂式、壁挂式和镶嵌式等多种方式。虽然安装LED透明屏不像安装空调等广告载体那样简单,但也并非极其复杂。考虑到LED透明屏的特殊性&…

LM+retrieval

retrievalLM https://acl2023-retrieval-lm.github.io/ 在input层利用retrieval信息 主要是通过通过相似度计算或者重要性计算在datasets中得到与询问x最相关的k个document,讲文档放在询问x前面组成新的LM的输入,获取额外知识以回答问题。 在intermediate layer…

nacos2.2.3 删除永久实例

问题描述 在nacos2.2.3中删除非临时性实例 报错 解决方案 在命令行下执行命令: curl -X DELETE "http://127.0.0.1:8848/nacos/v1/ns/instance?serviceNamenacos-restTemplate-stock&groupNameDEFAULT_GROUP&namespaceIdpublic&ip192.168.1…

全网最牛,接口自动化-Linux系统安装Jenkins+Ant详细步骤

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 在Linux系统上安装…

GPT-4助力数据分析:提升效率与洞察力的未来关键技术 | 京东云技术团队

摘要 随着大数据时代的到来,数据分析已经成为企业和组织的核心竞争力。然而,传统的数据分析方法往往无法满足日益增长的数据分析需求的数量和复杂性。在这种背景下,ChatGPT-4作为一种先进的自然语言处理技术,为数据分析带来了革命…

FastAPI和Flask:构建RESTful API的比较分析

Python 是一种功能强大的编程语言,广泛应用于 Web 开发领域。FastAPI 和 Flask 是 Python Web 开发中最受欢迎的两个框架。本文将对 FastAPI 和 Flask 进行综合对比,探讨它们在语法和表达能力、生态系统和社区支持、性能和扩展性、开发工具和调试支持、安…

一键批量删除文件名中的空格,轻松整理您的文件

随着数字化时代的到来,我们的电脑里积攒了越来越多的文件,但是随之而来的问题是,文件名中的空格可能会导致一些不便和混乱。为了解决这一问题,我们开发了一款便捷实用的工具,可以一键批量删除文件名中的空格&#xff0…

ORM 之 阿里 Fluent-Mybatis主推动态SQL 你学废了吗?

Mybatis作为在东亚开发者市场上占有绝对的使用优势,在中国大陆上讨论Mybatis优化的项目也是挺活跃。 局限于原始Mybatis繁琐的流程,自动代码生成、声明式SQL、动态SQL,以及诸多细节的内容:多租户、多数据源、数据脱敏、SQL审计、…

MyBatis查询数据库之三(#{}vs${},like查询,resultMap,as,多表查询)

目录 查询操作 1.单表查询 1.1 参数占位符#{}和${} 1.2 ${}的优点 1.3 sql注入问题 ​编辑 面试常问:${}与#{}的区别 1.4 like查询 2.多表查询 2.1 返回字典映射:resultMap 2.2 多表查询 (1)建立 Articalinfo 实体类&a…

所有AI图都在这了

最近一直在玩AI生图,有点拔不出来了,喜欢生图的感觉,日积月累,已经有几千张图片了,想把这些图片都分享出来给大家欣赏,云盘、图片APP,感觉都不好用,最后熬夜码了一个属于自己的图片管…

(树) 剑指 Offer 68 - II. 二叉树的最近公共祖先 ——【Leetcode每日一题】

❓剑指 Offer 68 - II. 二叉树的最近公共祖先 难度:简单 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科 中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是…

2462. 雇佣 K 位工人的总代价

题目描述&#xff1a; 主要思路&#xff1a; 分别维护两个堆&#xff0c;取左右两边最小的那个。 class Solution { public:long long totalCost(vector<int>& costs, int k, int candidates) {priority_queue<int,vector<int>,greater<int>> ql…

清洗无效邮箱地址,让邮件营销更高效

作为一种低成本、高回报的营销手段&#xff0c;电子邮件营销因其传播效率高、营销范围广的特点被广泛应用。虽然现在短视频营销、搜索引擎营销、自媒体营销、内容营销等各种网络营销方式层出不穷。但是在整个网络营销宣传活动中&#xff0c;大多数企业还是会选择把邮件营销作为…

【Java从入门到大牛】File和IO流上篇

&#x1f525; 本文由 程序喵正在路上 原创&#xff0c;CSDN首发&#xff01; &#x1f496; 系列专栏&#xff1a;Java从入门到大牛 &#x1f320; 首发时间&#xff1a;2023年8月9日 &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e…

《合成孔径雷达成像算法与实现》Figure3.5

clc clear all close all%参数设置 TBP 100; %时间带宽积 T 10e-6; %脉冲持续时间%参数计算 B TBP/T; %信号带宽 K B/T; …

如何简化大型网络服务提供商中的DDoS防护事宜

分布式拒绝服务&#xff08;DDoS&#xff09;攻击对服务提供商构成重大威胁&#xff0c;它们有可能破坏关键基础设施并扰乱业务运营。然而对于大型服务提供商而言实施和管理有效的DDoS防护解决方案可能非常复杂且成本高昂。 下面&#xff0c;火伞云将和大家一起探讨如何简化大型…