基于pytorch的视觉变换器-Vision Transformer(ViT)的介绍与应用

news2025/2/27 22:59:39

近年来,计算机视觉领域因变换器模型的出现而发生了革命性变化。最初为自然语言处理任务设计的变换器,在捕捉视觉数据的空间依赖性方面也显示出了惊人的能力。视觉变换器(Vision Transformer,简称ViT)就是这种变革的一个典型例子,它提出了一种新颖的架构,在各种图像分类任务上实现了最先进的性能。

在这篇文章中,我们将一起构建我们自己的视觉变换器模型,使用PyTorch进行实现。通过逐步分解实现过程,我们旨在提供对ViT架构的全面理解,并使您能够清晰地掌握其内部工作原理。当然,我们可以直接使用PyTorch内置的视觉变换器模型,但那样就没有乐趣了。

我们将从设置必要的依赖和库开始,确保整个项目的顺利进行。接下来,我们将进入数据获取阶段,获取适合训练我们的视觉变换器模型的数据集。

为了准备训练数据,我们将定义必要的转换操作,用于增强和标准化输入图像。随着数据转换的到位,我们将创建自定义数据集和数据加载器,为训练模型搭建舞台。

要理解视觉变换器架构,从零开始构建它至关重要。在后续部分,我们将逐一解析ViT模型的每个组件,并解释其作用。我们将从负责将输入图像划分为更小块并将其嵌入向量格式的Patch嵌入层开始。紧接着,我们将探索多头自注意力模块,它允许模型捕捉图像块之间的全局和局部关系。

此外,我们还将深入了解机器学习感知器模块,这是一个关键组件,使模型能够捕捉输入数据的层次性表示。通过组合这些组件,我们将构建变换器块,它构成了视觉变换器的核心构建块。

最后,我们将通过使用我们精心打造的组件来创建ViT模型。随着模型的完成,我们可以对其进行实验,微调,并在各种计算机视觉任务上发挥其潜力。

到本文结束时,您将获得对视觉变换器架构及其在PyTorch中的实现的扎实理解。有了这些知识,您将能够根据自己的具体需求修改和扩展模型,甚至在此基础上构建更高级的计算机视觉应用。让我们开始吧。

快速部署

第一步:安装并导入依赖项

!pip install -q torchinfo
import torch
from torch import nn
from torchinfo import summary

第二步:获取数据集

import requests
from pathlib import Path
import os
from zipfile import ZipFile

# 定义zip文件的URL
url = "https://github.com/mrdbourke/pytorch-deep-learning/raw/main/data/pizza_steak_sushi.zip"

# 发送GET请求以下载文件
response = requests.get(url)

# 定义数据目录的路径
data_path = Path("data")

# 定义图像目录的路径
image_path = data_path / "pizza_steak_sushi"

# 检查图像目录是否已经存在
if image_path.is_dir():
    print(f"{image_path} 目录已存在。")
else:
    print(f"未找到 {image_path} 目录,正在创建...")
    image_path.mkdir(parents=True, exist_ok=True)

# 将下载的内容写入一个zip文件
with open(data_path / "pizza_steak_sushi.zip", "wb") as f:
    f.write(response.content)

# 解压zip文件到图像目录
with ZipFile(data_path / "pizza_steak_sushi.zip", "r") as zipref:
    zipref.extractall(image_path)

# 删除下载的zip文件
os.remove(data_path / 'pizza_steak_sushi.zip')

第三步:定义转化器

1.调正图像大小
2.运用ToTensor()函数转化格式

from torchvision.transforms import Resize, Compose, ToTensor

# 使用Compose定义train_transform
train_transform = Compose([
    Resize((224, 224)),  # 将图像调整为224x224像素
    ToTensor()           # 将图像转换为Tensor格式
])

# 使用Compose定义test_transform
test_transform = Compose([
    Resize((224, 224)),  # 将图像调整为224x224像素
    ToTensor()           # 将图像转换为Tensor格式
])

第四步:创建数据集和数据加载器

我们可以使用PyTorch的ImageFolder数据集库来创建我们的数据集。
为了使ImageFolder工作,您的数据文件夹需要按照这种方式进行结构化。

data
└── pizza_steak_sushi
    ├── test
    │   ├── pizza
    │   ├── steak
    │   └── sushi
    └── train
        ├── pizza
        ├── steak
        └── sushi

所有的比萨图片将位于train和test子文件夹下的pizza文件夹中,其他类别的图片也是如此。
在创建的training_dataset和test_dataset上,有两个有用的方法可以调用:

  1. training_dataset.classes,它会返回['pizza', 'steak', 'sushi']
  2. training_dataset.class_to_idx,它会返回{'pizza': 0, 'steak': 1, 'sushi': 2}
from torchvision.datasets import ImageFolder
from torch.utils.data import DataLoader

BATCH_SIZE = 32

# 定义数据目录
data_dir = Path("data/pizza_steak_sushi")

# 使用ImageFolder创建训练数据集
training_dataset = ImageFolder(root=data_dir / "train", transform=train_transform)

# 使用ImageFolder创建测试数据集
test_dataset = ImageFolder(root=data_dir / "test", transform=test_transform)

# 使用DataLoader创建训练数据加载器
training_dataloader = DataLoader(
    dataset=training_dataset,
    shuffle=True,                # 开启随机打乱
    batch_size=BATCH_SIZE,       # 批量大小设置为32
    num_workers=2                # 使用2个工作进程
)

# 使用DataLoader创建测试数据加载器
test_dataloader = DataLoader(
    dataset=test_dataset,
    shuffle=False,               # 关闭随机打乱
    batch_size=BATCH_SIZE,       # 批量大小设置为32
    num_workers=2                # 使用2个工作进程
)

我们可以可视化一些训练数据集图像,并查看它们的标签

import matplotlib.pyplot as plt
import random

num_rows = 5
num_cols = num_rows

# 创建一个包含多个子图的图形
fig, axs = plt.subplots(num_rows, num_cols, figsize=(10, 10))

# 遍历子图并展示训练数据集中的随机图片
for i in range(num_rows):
    for j in range(num_cols):
        # 从训练数据集中随机选择一个索引
        image_index = random.randrange(len(training_dataset))

        # 在子图中显示图片
        axs[i, j].imshow(training_dataset[image_index][0].permute((1, 2, 0)))

        # 设置子图的标题为对应的类名
        axs[i, j].set_title(training_dataset.classes[training_dataset[image_index][1]], color="white")

        # 禁用坐标轴以获得更好的视觉效果
        axs[i, j].axis(False)

# 设置图形的超标题
fig.suptitle(f"训练数据集中随机{num_rows * num_cols}张图片", fontsize=16, color="white")

# 将图形的背景色设置为黑色
fig.set_facecolor(color='black')

# 展示绘图
plt.show()

在这里插入图片描述

理解视觉变换器架构

让我们现在花些时间来理解视觉变换器架构。这是原始视觉变换器论文的链接:https://arxiv.org/abs/2010.11929。
下面,您可以看到论文中提出的架构图。
在这里插入图片描述

视觉变换器(ViT)是一种为图像处理任务设计的变换器架构类型。与传统的变换器不同,后者主要处理词嵌入序列,ViT处理的是图像嵌入序列。换句话说,它将输入图像分割成多个小块,并将这些小块视为一系列可学习的嵌入。

ViT的基本操作步骤包括:

  1. 创建补丁嵌入:它将图像分割成补丁,并将每个补丁转换成嵌入向量。
  2. 通过变换器块传递嵌入
    • 补丁嵌入连同一个分类令牌一起,被送入多个变换器块。
    • 每个变换器块由一个多头自注意力模块(MultiHead Self-Attention Block,简称MSA块)和一个多层感知器块(Multi-Layer Perceptron Block,简称MLP块)组成。
    • 在变换器块的输入和MSA块的输入之间,以及MLP块的输入和MLP块的输出之间建立跳过连接,以缓解梯度消失问题。
  3. 执行分类
    • 变换器块的最终输出通过一个MLP块进行处理。
    • 使用其中的分类令牌,该令牌包含有关输入图像类别的信息,来进行预测。

接下来,我们将详细探讨每个步骤,首先从创建补丁嵌入的关键过程开始。

第五步:创建补丁嵌入层

根据ViT论文,我们需要对图像执行以下操作,然后才能传递给多头自注意力变换器层:

  1. 将图像转换为16x16像素大小的补丁。
  2. 将每个补丁嵌入768维空间。因此,每个补丁变成一个[1 x 768]向量。每张图像将有KaTeX parse error: Can't use function '\(' in math mode at position 1: \̲(̲N = H \times W …个这样的补丁。这样会得到形状为[14 x 14 x 768]的图像。
  3. 将图像沿着一个向量展平,得到一个[196 x 768]矩阵,即我们的图像嵌入序列。
  4. 在上述输出前加上类别令牌嵌入。
  5. 将位置嵌入添加到类别令牌和图像嵌入中。
PATCH_SIZE = 16
IMAGE_WIDTH = 224
IMAGE_HEIGHT = IMAGE_WIDTH
IMAGE_CHANNELS = 3
EMBEDDING_DIMS = IMAGE_CHANNELS * PATCH_SIZE**2
NUM_OF_PATCHES = int((IMAGE_WIDTH * IMAGE_HEIGHT) / PATCH_SIZE**2)

# 图像宽度和高度应该能被补丁大小整除。这是一个检查以确保这一点。
assert IMAGE_WIDTH % PATCH_SIZE == 0 and IMAGE_HEIGHT % PATCH_SIZE == 0, print("图像宽度不是补丁大小的整数倍")
4.1 将图像转换为16x16大小的补丁,并为每个大小为768的补丁创建一个嵌入向量。

这可以通过使用一个Conv2D层来实现,其kernel_size(核大小)等于patch_size(补丁大小),stride(步长)也等于patch_size。
在这里插入图片描述

conv_layer = nn.Conv2d(in_channels = IMAGE_CHANNELS, out_channels = EMBEDDING_DIMS, kernel_size = PATCH_SIZE, stride = PATCH_SIZE)

我们可以将随机图像传递到卷积层中,看看会发生什么

random_images, random_labels = next(iter(training_dataloader))
random_image = random_images[0]

# 创建一个新的图形
fig = plt.figure(1)

# 展示随机选取的图像
plt.imshow(random_image.permute((1, 2, 0)))

# 禁用坐标轴以获得更好的视觉效果
plt.axis(False)

# 设置图像的标题
plt.title(training_dataset.classes[random_labels[0]], color="white")

# 将图形的背景色设置为黑色
fig.set_facecolor(color="black")

在这里插入图片描述
我们需要将形状更改为[1,14,14,768],并将输出扁平化为[1,196,768]

# 将图像通过卷积层
image_through_conv = conv_layer(random_image.unsqueeze(0))
print(f'通过卷积层的嵌入形状 -> {list(image_through_conv.shape)} <- [批大小, 补丁行数, 补丁列数, 嵌入维度]')

# 调整image_through_conv的维度以匹配预期形状
image_through_conv = image_through_conv.permute((0, 2, 3, 1))

# 使用nn.Flatten创建一个展平层
flatten_layer = nn.Flatten(start_dim=1, end_dim=2)

# 将image_through_conv通过展平层
image_through_conv_and_flatten = flatten_layer(image_through_conv)

# 打印嵌入图像的形状
print(f'通过展平层的嵌入形状 -> {list(image_through_conv_and_flatten.shape)} <- [批大小, 补丁数量, 嵌入维度]')

# 将嵌入图像赋值给一个变量
embedded_image = image_through_conv_and_flatten
通过卷积层的嵌入形状 -> [1, 768, 14, 14] <- [批大小, 补丁行数, 补丁列数, 嵌入维度]
通过展平层的嵌入形状 -> [1, 196, 768] <- [批大小, 补丁数量, 嵌入维度]
4.2 添加类别标记嵌入和位置编码嵌入
class_token_embeddings = nn.Parameter(torch.rand((1, 1,EMBEDDING_DIMS), requires_grad  = True))
print(f'类别令牌嵌入的形状 --> {list(class_token_embeddings.shape)} <-- [批大小, 1, 嵌入维度]')

embedded_image_with_class_token_embeddings = torch.cat((class_token_embeddings, embedded_image), dim = 1)
print(f'\n带有类别令牌嵌入的图像嵌入形状 --> {list(embedded_image_with_class_token_embeddings.shape)} <-- [批大小, 补丁数量+1, 嵌入维度]')

position_embeddings = nn.Parameter(torch.rand((1, NUM_OF_PATCHES+1, EMBEDDING_DIMS ), requires_grad = True ))
print(f'\n位置嵌入的形状 --> {list(position_embeddings.shape)} <-- [批大小, 补丁数量+1, 嵌入维度]')

final_embeddings = embedded_image_with_class_token_embeddings + position_embeddings
print(f'\n最终嵌入的形状 --> {list(final_embeddings.shape)} <-- [批大小, 补丁数量+1, 嵌入维度]')
`class_token_embeddings` 的形状 --> `[1, 1, 768]` <-- `[批大小, 1, 嵌入维度]`

带有 `class_token_embeddings` 的图像嵌入形状 --> `[1, 197, 768]` <-- `[批大小, 补丁数量+1, 嵌入维度]`

`position_embeddings` 的形状 --> `[1, 197, 768]` <-- `[批大小, 补丁数量+1, 嵌入维度]`

`final_embeddings` 的形状 --> `[1, 197, 768]` <-- `[批大小, 补丁数量+1, 嵌入维度]`

将补丁嵌入层整合起来
我们将继承PyTorch的nn.Module来创建我们的自定义层,该层接收一张图像,并输出补丁嵌入,包括图像嵌入、类别标记嵌入和位置嵌入。

class PatchEmbeddingLayer(nn.Module):
    def __init__(self, in_channels, patch_size, embedding_dim,):
        super().__init__()
        self.patch_size = patch_size
        self.embedding_dim = embedding_dim
        self.in_channels = in_channels
        self.conv_layer = nn.Conv2d(in_channels=in_channels, out_channels=embedding_dim, kernel_size=patch_size, stride=patch_size)
        self.flatten_layer = nn.Flatten(start_dim=1, end_dim=2)
        self.class_token_embeddings = nn.Parameter(torch.rand((BATCH_SIZE, 1, EMBEDDING_DIMS), requires_grad=True))
        self.position_embeddings = nn.Parameter(torch.rand((1, NUM_OF_PATCHES + 1, EMBEDDING_DIMS), requires_grad=True))

    def forward(self, x):
        output = torch.cat((self.class_token_embeddings, self.flatten_layer(self.conv_layer(x).permute((0, 2, 3, 1)))), dim=1) + self.position_embeddings
        return output

让我们从我们的补丁嵌入层传递一批随机图像。

patch_embedding_layer = PatchEmbeddingLayer(in_channels=IMAGE_CHANNELS, patch_size=PATCH_SIZE, embedding_dim=IMAGE_CHANNELS * PATCH_SIZE ** 2)

patch_embeddings = patch_embedding_layer(random_images)
patch_embeddings.shape
torch.Size([32, 197, 768])
summary(model=patch_embedding_layer,
        input_size=(BATCH_SIZE, 3, 224, 224), # (batch_size, input_channels, img_width, img_height)
        col_names=["input_size", "output_size", "num_params", "trainable"],
        col_width=20,
        row_settings=["var_names"])

在这里插入图片描述

第六步 创建多头自注意力(Multi-Head Self Attention,MSA)块。

理解MSA块
作为将变换器块整合进视觉变换器模型的第一步,我们将创建一个多头自注意力块。
让我们花一点时间来理解MSA块。MSA块本身包含一个层归一化(LayerNorm)层和多头注意力层。层归一化层基本上是在嵌入维度上对我们的补丁嵌入数据进行规范化。多头注意力层接受输入数据作为三种形式的可学习向量,即查询(query)、键(key)和值(value),统称为qkv向量。这些向量共同构建了输入序列中每个补丁与同一序列中其他所有补丁之间的关系(因此得名自注意力)。
因此,我们输入MSA块的形状将是我们使用补丁嵌入层制作的补丁嵌入的形状 -> [批大小, 序列长度, 嵌入维度]。而从MSA层的输出形状将与输入形状相同。
MSA块代码
现在让我们开始编写我们MSA块的代码。这将是简短的,因为PyTorch已经有了LayerNorm和MultiHeadAttention层的预构建实现。我们只需要传递正确的参数以适应我们的架构。我们可以在原始ViT论文中的这张表格中找到我们MSA块所需的各种参数。
在这里插入图片描述

class MultiHeadSelfAttentionBlock(nn.Module):
  def __init__(self,
               embedding_dims = 768, # 隐藏尺寸D,来自ViT论文表1
               num_heads = 12,  # 头的数量,来自ViT论文表1
               attn_dropout = 0.0 # 默认为零,因为根据ViT论文,MSA块没有dropout
               ):
    super().__init__()

    self.embedding_dims = embedding_dims
    self.num_head = num_heads
    self.attn_dropout = attn_dropout

    self.layernorm = nn.LayerNorm(normalized_shape = embedding_dims)

    self.multiheadattention =  nn.MultiheadAttention(num_heads = num_heads,
                                                     embed_dim = embedding_dims,
                                                     dropout = attn_dropout,
                                                     batch_first = True,
                                                    )

  def forward(self, x):
    x = self.layernorm(x)
    output,_ = self.multiheadattention(query=x, key=x, value=x,need_weights=False)
    return output

测试

multihead_self_attention_block = MultiHeadSelfAttentionBlock(embedding_dims = EMBEDDING_DIMS,
                                                             num_heads = 12
                                                             )
print(f'Shape of the input Patch Embeddings => {list(patch_embeddings.shape)} <= [batch_size, num_patches+1, embedding_dims ]')
print(f'Shape of the output from MSA Block => {list(multihead_self_attention_block(patch_embeddings).shape)} <= [batch_size, num_patches+1, embedding_dims ]')

Shape of the input Patch Embeddings => [32, 197, 768] <= [batch_size, num_patches+1, embedding_dims ]
Shape of the output from MSA Block => [32, 197, 768] <= [batch_size, num_patches+1, embedding_dims ]

很好,看起来我们的MSA块正在工作。我们可以使用 torchinfo 获取有关 MSA 块的更多信息

summary(model=multihead_self_attention_block,
        input_size=(1, 197, 768), # (batch_size, num_patches, embedding_dimension)
        col_names=["input_size", "output_size", "num_params", "trainable"],
        col_width=20,
        row_settings=["var_names"])

在这里插入图片描述

第七步 创建机器学习感知器(Machine Learning Perceptron,MLP)块

理解MLP块

变换器中的机器学习感知器(MLP)块是一个全连接层(也称为线性层或密集层)和非线性层的组合。在ViT中,非线性层是一个GeLU层。
在这里插入图片描述

变换器还实现了一个Dropout层以减少过拟合。因此,MLP块将如下所示:
输入 → 线性 → GeLU → Dropout → 线性 → Dropout
根据论文,第一个线性层将嵌入维度扩展到3072维(对于ViT-16/Base)。Dropout设置为0.1,第二个线性层将维度缩减回嵌入维度。

MLP块代码

现在让我们组装我们的MLP块。根据ViT论文,从MSA块输出的内容加上输入到MSA块的内容(由模型架构图中的跳过/残差连接表示)作为输入传递给MLP块。所有层都由PyTorch库提供。我们只需要将它们组装起来。

class MachineLearningPerceptronBlock(nn.Module):
  def __init__(self, embedding_dims, mlp_size, mlp_dropout):
    super().__init__()
    self.embedding_dims = embedding_dims
    self.mlp_size = mlp_size
    self.dropout = mlp_dropout

    self.layernorm = nn.LayerNorm(normalized_shape = embedding_dims)
    self.mlp = nn.Sequential(
        nn.Linear(in_features = embedding_dims, out_features = mlp_size),
        nn.GELU(),
        nn.Dropout(p = mlp_dropout),
        nn.Linear(in_features = mlp_size, out_features = embedding_dims),
        nn.Dropout(p = mlp_dropout)
    )

  def forward(self, x):
    return self.mlp(self.layernorm(x))

测试

mlp_block = MachineLearningPerceptronBlock(embedding_dims = EMBEDDING_DIMS,
                                           mlp_size = 3072,
                                           mlp_dropout = 0.1)

summary(model=mlp_block,
        input_size=(1, 197, 768), # (batch_size, num_patches, embedding_dimension)
        col_names=["input_size", "output_size", "num_params", "trainable"],
        col_width=20,
        row_settings=["var_names"])

在这里插入图片描述

第八步:集成组合

class TransformerBlock(nn.Module):
  def __init__(self, embedding_dims = 768,
               mlp_dropout=0.1,
               attn_dropout=0.0,
               mlp_size = 3072,
               num_heads = 12,
               ):
    super().__init__()

    self.msa_block = MultiHeadSelfAttentionBlock(embedding_dims = embedding_dims,
                                                 num_heads = num_heads,
                                                 attn_dropout = attn_dropout)

    self.mlp_block = MachineLearningPerceptronBlock(embedding_dims = embedding_dims,
                                                    mlp_size = mlp_size,
                                                    mlp_dropout = mlp_dropout,
                                                    )

  def forward(self,x):
    x = self.msa_block(x) + x
    x = self.mlp_block(x) + x

    return x

测试

transformer_block = TransformerBlock(embedding_dims = EMBEDDING_DIMS,
                                     mlp_dropout = 0.1,
                                     attn_dropout=0.0,
                                     mlp_size = 3072,
                                     num_heads = 12)

print(f'Shape of the input Patch Embeddings => {list(patch_embeddings.shape)} <= [batch_size, num_patches+1, embedding_dims ]')
print(f'Shape of the output from Transformer Block => {list(transformer_block(patch_embeddings).shape)} <= [batch_size, num_patches+1, embedding_dims ]')

Shape of the input Patch Embeddings => [32, 197, 768] <= [batch_size, num_patches+1, embedding_dims ]
Shape of the output from Transformer Block => [32, 197, 768] <= [batch_size, num_patches+1, embedding_dims ]
summary(model=transformer_block,
        input_size=(1, 197, 768), # (batch_size, num_patches, embedding_dimension)
        col_names=["input_size", "output_size", "num_params", "trainable"],
        col_width=20,
        row_settings=["var_names"])

在这里插入图片描述

第九步:创建ViT模型

最后,让我们来组装我们的ViT模型。这将是非常简单的,只需将我们到目前为止所做的所有工作结合起来。我们将稍微增加一个分类器层。在ViT中,分类器层是一个简单的线性层,配有层归一化。分类是在变换器输出的零索引处进行的。

class ViT(nn.Module):
  def __init__(self, img_size = 224,
               in_channels = 3,
               patch_size = 16,
               embedding_dims = 768,
               num_transformer_layers = 12, # from table 1 above
               mlp_dropout = 0.1,
               attn_dropout = 0.0,
               mlp_size = 3072,
               num_heads = 12,
               num_classes = 1000):
    super().__init__()

    self.patch_embedding_layer = PatchEmbeddingLayer(in_channels = in_channels,
                                                     patch_size=patch_size,
                                                     embedding_dim = embedding_dims)

    self.transformer_encoder = nn.Sequential(*[TransformerBlock(embedding_dims = embedding_dims,
                                              mlp_dropout = mlp_dropout,
                                              attn_dropout = attn_dropout,
                                              mlp_size = mlp_size,
                                              num_heads = num_heads) for _ in range(num_transformer_layers)])

    self.classifier = nn.Sequential(nn.LayerNorm(normalized_shape = embedding_dims),
                                    nn.Linear(in_features = embedding_dims,
                                              out_features = num_classes))

  def forward(self, x):
    return self.classifier(self.transformer_encoder(self.patch_embedding_layer(x))[:, 0])
summary(model=vit,
        input_size=(BATCH_SIZE, 3, 224, 224), # (batch_size, num_patches, embedding_dimension)
        col_names=["input_size", "output_size", "num_params", "trainable"],
        col_width=20,
        row_settings=["var_names"])

在这里插入图片描述

就是这样。现在,您可以像训练PyTorch中的任何其他模型一样训练这个模型。请告诉我这对您来说效果如何。我希望这个逐步指南帮助您理解了视觉变换器,并激发您更深入地探索计算机视觉和变换器模型的世界。借助获得的知识,您已经具备了推动计算机视觉发展和解锁这些开创性架构潜能的能力。

所以,继续前进,基于您所学到的知识,让您的想象力在利用视觉变换器应对激动人心的视觉挑战时自由发挥。编码愉快!

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

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

相关文章

链表基础知识详解

链表基础知识详解 一、链表是什么&#xff1f;1.链表的定义2.链表的组成3.链表的优缺点4.链表的特点 二、链表的基本操作1.链表的建立2.链表的删除3.链表的查找4.链表函数 一、链表是什么&#xff1f; 1.链表的定义 链表是一种物理存储单元上非连续、非顺序的存储结构&#xf…

人工智能|机器学习——K-means系列聚类算法k-means/ k-modes/ k-prototypes/ ......(划分聚类)

1.k-means聚类 1.1.算法简介 K-Means算法又称K均值算法&#xff0c;属于聚类&#xff08;clustering&#xff09;算法的一种&#xff0c;是应用最广泛的聚类算法之一。所谓聚类&#xff0c;即根据相似性原则&#xff0c;将具有较高相似度的数据对象划分至同一类簇&#xff0c;…

【Docker】golang使用DockerFile正确食用指南

【Docker】golang使用DockerFile正确食用指南 大家好 我是寸铁&#x1f44a; 总结了一篇golang使用DockerFile正确食用指南✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 问题背景 今天寸铁想让编写好的go程序在docker上面跑&#xff0c;要想实现这样的效果&#xff0c;就需要用…

流放者柯南服务器端搭建!

这是一个开放世界生存游戏。在这个游戏里&#xff0c;你可以建造自己的城堡&#xff0c;探索神秘的遗迹&#xff0c;与野兽和敌人战斗&#xff0c;甚至成为一个神。 首先推荐服务器配置&#xff1a; 4核16G 月50 季度200 8核32G 月120 季度350 含安装搭建服务&#xff01…

《剑指 Offer》专项突破版 - 面试题 76 : 数组中第 k 大的数字(C++ 实现)

目录 详解快速排序 面试题 76 : 数组中第 k 大的数字 详解快速排序 快速排序是一种非常高效的算法&#xff0c;从其名字可以看出这种排序算法最大的特点是快。当表现良好时&#xff0c;快速排序的速度比其他主要对手&#xff08;如归并排序&#xff09;快 2 ~ 3 倍。 快速排…

WordPress高端后台美化WP Adminify Pro优化版

后台UI美化WP Adminify Pro修改自定义插件&#xff0c;适合建站公司和个人使用&#xff0c;非常高大上&#xff0c;下载地址&#xff1a;WP Adminify Pro优化版 修复记录&#xff1a; 1、修复已知BUG 2、修复手机版兼容问题 3、修复打开速度&#xff0c;原版打开速度太慢 4…

Git的基本操作(安装Git,创建本地仓库,配置Git,添加、修改、回退、撤销修改、删除文件)

文章目录 一、Git安装二、创建本地仓库三、配置Git四、认识工作区、暂存区、本地库五、添加文件六、修改文件七、版本回退八、撤销修改1.对于⼯作区的代码&#xff0c;还没有add2.已经add&#xff0c;但没有commit3.已经add&#xff0c;并且已经commit 九、删除⽂件 一、Git安装…

解释区块链技术的应用场景、优势及经典案例

目录 1.区块链应用场景 2.区块链优势 3.区块链经典案例 区块链技术是一种分布式账本技术&#xff0c;它通过加密和安全验证机制&#xff0c;允许网络中的多个参与者之间进行可信的、不可篡改的交易和数据的记录与传输。区块链技术的应用场景广泛&#xff0c;其优势也十分显著…

R语言复现:中国Charls数据库一篇现况调查论文的缺失数据填补方法

编者 在临床研究中&#xff0c;数据缺失是不可避免的&#xff0c;甚至没有缺失&#xff0c;数据的真实性都会受到质疑。 那我们该如何应对缺失的数据&#xff1f;放着不管&#xff1f;还是重新开始?不妨试着对缺失值进行填补&#xff0c;简单又高效。毕竟对于统计师来说&#…

【AcWing】蓝桥杯集训每日一题Day1|二分|差分|503.借教室(C++)

503. 借教室 503. 借教室 - AcWing题库难度&#xff1a;简单时/空限制&#xff1a;1s / 128MB总通过数&#xff1a;8052总尝试数&#xff1a;26311来源&#xff1a;NOIP2012提高组算法标签二分差分 题目内容 在大学期间&#xff0c;经常需要租借教室。 大到院系举办活动&…

Yolov8模型用torch_pruning剪枝

目录 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; 原理 遍历所有分组 高级剪枝器 &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680…

TYPE C模拟耳机POP音产生缘由

关于耳机插拔的POP音问题&#xff0c;小白在之前的文章中讲述过关于3.5mm耳机的POP音产生原因。其实这类插拔问题的POP音不仅仅存在于3.5mm耳机&#xff0c;就连现在主流的Type C模拟耳机的插拔也存在此问题&#xff0c;今天小白就来讲一讲这类耳机产生POP音的缘由。 耳机左右…

计算机视觉——P2PNet基于点估计的人群计数原理与C++模型推理

简介 人群计数是计算机视觉领域的一个核心任务&#xff0c;旨在估算静止图像或视频帧中的行人数量。在过去几十年中&#xff0c;研究人员在这个领域投入了大量的精力&#xff0c;并在提高现有主流基准数据集性能方面取得了显著进展。然而&#xff0c;训练卷积神经网络需要大规…

书与我

和书深深结缘&#xff0c;始于需求&#xff0c;得益于通勤时间长。 读什么书 一直没有停止过编码&#xff0c;工作性质也要求我必须了解很多的新技术&#xff0c;从踏上工作岗位后&#xff0c;就需要不停的看书。从《JAVA编程思想》、《java与模式》、《TCP/IP详解》、《深入…

131.分割回文串

// 定义一个名为Solution的类 class Solution {// 声明一个成员变量&#xff0c;用于存储所有满足条件的字符串子序列划分结果List<List<String>> lists new ArrayList<>(); // 声明一个成员变量&#xff0c;使用LinkedList实现的双端队列&#xff0c;用于临…

Windows下安装pip

一、下载pip 官网地址&#xff1a;https://pypi.org/project/pip/#files 1.1、pip工具查找方法 单击官网首页“PyPi”选项 在弹出来的搜索框中输入“pip” 选择最新的pip版本&#xff0c;点进去 下载pip安装包包 二、安装pip 解压“pip-24.0.tar.gz”&#xff0c;进…

【深度学习笔记】6_5 RNN的pytorch实现

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.5 循环神经网络的简洁实现 本节将使用PyTorch来更简洁地实现基于循环神经网络的语言模型。首先&#xff0c;我们读取周杰伦专辑歌词…

b站小土堆pytorch学习记录—— P23-P24 损失函数、反向传播和优化器

文章目录 一、损失函数1.简要介绍2.代码 二、优化器1.简要介绍2.代码 一、损失函数 1.简要介绍 可参考博客&#xff1a; 常见的损失函数总结 损失函数的全面介绍 pytorch学习之十九种损失函数 损失函数&#xff08;Loss Function&#xff09;是用来衡量模型预测输出与实际…

开发指南002-前后端信息交互规范-概述

前后端之间采用restful接口&#xff0c;服务和服务之间使用feign。信息交互遵循如下平台规范&#xff1a; 前端&#xff1a; 建立api目录&#xff0c;按照业务区分建立不同的.js文件&#xff0c;封装对后台的调用操作。其中qlm*.js为平台预制的接口文件&#xff0c;以qlm_user.…

离线数仓(五)【数据仓库建模】

前言 今天开始正式数据仓库的内容了, 前面我们把生产数据 , 数据上传到 HDFS , Kafka 的通道都已经搭建完毕了, 数据也就正式进入数据仓库了, 解下来的数仓建模是重中之重 , 是将来吃饭的家伙 ! 以及 Hive SQL 必须熟练到像喝水一样 ! 第1章 数据仓库概述 1.1 数据仓库概念 数…