[深度学习]图片分类任务
图片分类任务
文章目录
- 图片分类任务
- 分类任务
- 回归和分类
- 如何做分类的输出
- 图片分类
- 卷积神经网络
- 保持特征图大小不变
- 更大的卷积核和更多的卷积核层数
- 特征图怎么变小
- 卷积神经网络中特征图改变
- 卷积到全连接
- 分类任务的LOSS
- 一个基本的分类神经网络
- 经典神经网络
- AlexNet
- VggNet
- ResNet
- 神经网络透析
- 卷积和全连接的关系
- 深度学习= 玩特征
分类任务
许多现实任务,都不是预测问题(预测一个值,是在一个连续的范围取结果),而是分类问题。分类问题就像一道选择题,比如判断当前的动物是猫🐱还是狗🐕,问题的答案要么是猫要么是狗,是一个离散的结果。
回归和分类
从数据的角度看,回归任务和分类任务的区别:

- 回归任务是找到一个模型,模型能够预测输入的位置。
- 分类任务是找的一个模型,模型能够将不同类型的数据分隔开来。
如何做分类的输出
从训练流程上看,回归任务和分类任务的区别是模型的输入和输出是不同的,输入是给定的,但输出的类型是值得研究的,如果像回归任务一样通过深度神经网络,在神经网络的最后一层为输出维度为1,即使用nn.Linear(dim,1),然后根据得到的预测值与表示不同类型的值的关系的远近,是不合理的。
举个例子,假设预测值和各类型所表示的数据的距离关系如下:

如果是通过预测值和各类型所表示的数据的距离的远近来判断是哪个类型,离0最近,也就是任务是0表示的类型,但也默认了一件事,相比4表示的类型,更可能是1表示的类型,因为距离不同,但是分类任务的性质是每个类型是等价的,如果有0,1,2,3,4几个类型,是0类型,就不可能是别的类型,其他类型没有区别,因此该方法是存在问题的。
分类任务的标签Y是用独热编码表示类型,模型的输出结果是对应长度的向量,认为最大值所在的下标为预测类。

图片分类
图片分类的输入X为表示图片的矩阵,输出为类型。

由于模型的输入类型是确定的,通过规定输入的图片为224*224大小的,模型得到的预测值为图片为各个类型的概率,LOSS采用交叉熵的方式进行计算。
卷积神经网络
图片的表示方式通常是RGB三元色表示矩阵,三种颜色分别对应一个表示矩阵,图片分类的输入为一个3*224*224的矩阵,为了能通过神经网络模型,使用全连接方式,我们尝试把图片矩阵“拉直“成三个向量,然后合并成一个向量输入可以吗?

答案是不行的,因为这样做的话输入为近十几万维度的向量,这样的数据量太大了,很容易出现过拟合等的问题。

为了解决以上的问题,引入卷积神经网络。为了引入卷积神经网络,我们举一个例子,我们来考虑如下场景:

人工智能让机器模仿人类的方式解决问题,因此我们先考虑一下对于这个问题,人类是如何思考的?小图是一个3*3的图像,因此将其与大图中每个3*3的子图进行对比,如果存在和小图一样的子图,就说明大图含有小图。仿照这个方式,让机器如何做呢?,在矩阵中1表示红色,-1表示黑色:

将大图和小图用矩阵表示:
让小图表示矩阵分别和大图中不同位置的子图矩阵进行比对,这个作用于不同位置的滚动过程称为”卷“,每次对比时让小图和子图的矩阵对应位置相乘然后求和称为”积“,得到一个新的矩阵:

将用于对比的小图称为卷积核,将能够进行卷积的图称为特征图。
假设要做的任务是,判断图片中是否为鸟类,卷积核中可以是鸟的某样有特点的部分,比如鸟嘴,鸟眼睛,鸟脚。如果图中是鸟,那么在卷积到图中相应位置,会得到数据较大的值,就可以判断图中大概率是鸟,因此不需要对整张图片都卷积:

由于卷积核的选取是不唯一的,因此卷积神经网路目标就是如何卷积核变得像我们想要的,最后得到一个有意义的特征图。

由于图片是由RGB三元色矩阵表示的,因此卷积核也要对应有3个矩阵,卷积时卷积核中三个矩阵和特征图子图对应矩阵的对应位置进行积。卷积核的大小称为权重(参数量),也称为神经元的感受野。假设如下图卷积核大小为3*3*3,那么每次”积“的时候就是27个乘积相加得到一个值,加入到特征图中。

保持特征图大小不变
直接使用特征图进行卷积,得到的特征图大小会比被卷积的特征图大小小:

为了统一规划,需要让特征图卷积后的大小不变,为此要给被卷积的特征图进行零填充,也就是在周围加上一圈零:

更大的卷积核和更多的卷积核层数
卷积核更大形象来说就是看到更多的特征信息,也就是更大的感受野:

要加深卷积神经网络的层数,就会有更多的卷积核层数。加深卷积神经网络层数就是多次对得到的特征图进行卷积,首先对原始图片进行卷积,得到特征图,有多个卷积核时,会得到多个特征图,若要继续进行卷积,需要将得到的多个特征图看成多层的特征图进行卷积,卷积核的层数要和其层数一致:

在卷积的过程中,原始图片是给出的,特征图是计算出的,因此卷积神经网络中模型就是卷积核,卷积核的数据量即为参数量(忽略偏置)。
手算卷积神经网络题目

举例计算题目6图例:

另外有计算公式为:卷出来的特征图的大小=特征图大小-卷积核大小+1。(大小为4,即为图片为4*4)注意零填充是在周围一圈一圈加零,零填充后图片大小为原大小加上2倍的padding。

举例计算题目6:
特征图怎么变小
扩大步长
将特征图中的像素点,每隔一个去掉一个,减少特征图的样本量,不影响判断图中的类型,也就是减少一部分像素点不影响判断:

扩大步长就是卷积不是一个格子一个格子来的,不会把每个对应子图进行卷积,如果对每个对应子图进行卷积,每次只移动一步,扩大步长是在对一个子图卷积后,和移动多步后得到的子图卷积:

采用扩大步长时,计算卷积得到的特征图大小有如下公式:
- O=(I-K+2P)/S+1
- I:特征图大小 K:卷积核大小 P: padding S :步长 。
比如:输入 3*224*224, 卷积核大小为11, padding = 2 Stride = 4 , 卷积核数量64, 问输出多少。 (64*55*55)
扩大步长存在的问题是会丢失信息,且引入计算。
池化(pooling)
池化是通过选择最大值或平均值等方法来保留该区域的重要信息,或者说用一个数据代表多个数据。如下图,用一个值代表原先2*2大小的数据:

池化分为最大池化和平均池化。
- 最大池化(Max Pooling):选择池化窗口内的最大值作为输出。它保留了局部区域内最显著的特征,常用于提取强特征。
- 平均池化(Average Pooling):计算池化窗口内所有值的平均值作为输出。它会更平滑地保留特征,不会像最大池化那样强调最强的特征。

- 最大池化更加常用,因为最大池化不引入计算,平均池化引入了计算,并且对于一个图片通过更关注最显著的特征。
Pooling(2)表示用一个数表示原有2*2的数据。AdaptiveAvgPool(7)表示将原图像池化成7*7大小的。假设有224*224大小的图片池化成7*7大小的图片,需要用一个数据表示原有32*32大小的数据。(从行的角度思考,列同理,原先有224行,变成7行就是每224/7=32变成一个数)
卷积神经网络中特征图改变
**改变特征图的厚度(通道数)需要用卷积核进行卷积,**每个卷积核卷积得到厚度为1的特征图,有几个卷积核就能得到多少厚度的特征图。改变特征图的大小使用池化。
深度学习是一个”黑盒子“,卷积和池化的顺序和次数怎么安排比较好是不确定的,但通常在卷积神经网络中卷积和池化交替使用,通道数一般要大,特征图大小一般要小。
特征图的参数量=通道数*特征图大小,特征图大小减小的速度通常比通道数增大的速度快,因此参数量是在减小的。
卷积到全连接
通过卷积减小数据量,是为了通过全连接得到预测值向量。


标签Y中的数据表示是某一类的概率,而全连接得到的输出显而易见不是概率,因为相加都不一定为1,因此和标签的值表示的含义不同,需要将其通过计算转化,将其输入softmax后得到的输出作为模型预测的概率分布,在代码中实现就是调用nn.Softmax:

分类任务的LOSS
分类任务的预测值和标签不是单纯的一个数值,不能用简单的相减的方法计算LOSS值,采用交叉熵的方式计算LOSS值。

库中提供的交叉熵计算函数只需要给出全连接输出和标签即可,全连接输出的转换函数自身会完成。
一个基本的分类神经网络

- 前向过程:将输入的特征图通过若干次卷积和池化,然后通过若干层全连接,将全连接输出通过softmax得到预测值,计算LOSS。
- 回传过程:根据损失函数对模型参数的梯度进行计算,然后通过优化算法(如梯度下降)调整模型参数,减少损失。
分类任务的模型要通过大量的数据训练才能有很好的效果。

经典神经网络
AlexNet
AlexNet 在 ImageNet LSVRC-2012的比赛中, 取得了top-5错误率为15.3%的成绩。 第二名 是30多的错误率。 AlexNet有6亿个参数和650,000个神经元,包 含5个卷积层,有些层后面跟了max-pooling层, 3个全连接层 自AlexNet,引起了深度学习的狂潮。

- relu:方便计算,一定程度减小梯度消失。
- dropout:在每一轮训练过程中,随机"丢弃"神经网络中一定比例的神经元(即设置为0),可以缓解过拟合。
- 归一化:消除数据量纲的影响。

归一化可以在样本中对某一列进行归一化,也可以对一批处理过的数据进行批归一化。

AlexNet模型

Conv2d表示卷积。Pool(3,2)表示每次池化大小为3*3的区域,池化区域的变化每次步长为2,实际效果等于行,列减半。具体来说,第一次取1,2,3行,第二次取3,4,5行。AdaPool(6)表示池化为6*6的大小。- 图中蓝色为特征图大小。
- 图中参数量忽略了偏置值,每个卷积核有一个偏置值。
模型代码表示
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as Fclass AlexNet(nn.Module):def __init__(self, num_classes):super(AlexNet, self).__init__()# 定义网络结构self.conv1 = nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2) # 输入3个通道,输出64个通道,卷积核大小11x11,步长4,填充2self.conv2 = nn.Conv2d(64, 192, kernel_size=5, padding=2) # 输入64个通道,输出192个通道,卷积核大小5x5,填充2self.conv3 = nn.Conv2d(192, 384, kernel_size=3, padding=1) # 输入192个通道,输出384个通道,卷积核大小3x3,填充1self.conv4 = nn.Conv2d(384, 256, kernel_size=3, padding=1) # 输入384个通道,输出256个通道,卷积核大小3x3,填充1self.conv5 = nn.Conv2d(256, 256, kernel_size=3, padding=1) # 输入256个通道,输出256个通道,卷积核大小3x3,填充1self.fc1 = nn.Linear(256 * 6 * 6, 4096) # 将卷积层输出展平成一维,输入到全连接层self.fc2 = nn.Linear(4096, 4096) # 第二个全连接层self.fc3 = nn.Linear(4096, num_classes) # 最后的分类层# 定义池化层self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2) # 最大池化层,卷积核大小3x3,步长2# 定义Dropout层self.dropout = nn.Dropout(p=0.5) # Dropout层,防止过拟合def forward(self, x):# 第一层卷积x = self.conv1(x) # 输入(224, 224, 3) -> 卷积(64, 55, 55)x = F.relu(x) # 激活函数x = self.maxpool(x) # 池化 -> 输出(64, 27, 27)# 第二层卷积x = self.conv2(x) # 输入(64, 27, 27) -> 卷积(192, 27, 27)x = F.relu(x) # 激活函数x = self.maxpool(x) # 池化 -> 输出(192, 13, 13)# 第三层卷积x = self.conv3(x) # 输入(192, 13, 13) -> 卷积(384, 13, 13)x = F.relu(x) # 激活函数# 第四层卷积x = self.conv4(x) # 输入(384, 13, 13) -> 卷积(256, 13, 13)x = F.relu(x) # 激活函数# 第五层卷积x = self.conv5(x) # 输入(256, 13, 13) -> 卷积(256, 13, 13)x = F.relu(x) # 激活函数x = self.maxpool(x) # 池化 -> 输出(256, 6, 6)# 展平特征图x = x.view(x.size(0), -1) # 将(256, 6, 6)展平成(256*6*6,)# 第一个全连接层x = self.fc1(x) # 输入到全连接层(4096)x = F.relu(x) # 激活函数x = self.dropout(x) # Dropout# 第二个全连接层x = self.fc2(x) # 输入到全连接层(4096)x = F.relu(x) # 激活函数x = self.dropout(x) # Dropout# 输出层x = self.fc3(x) # 最终输出 (num_classes)return x
VggNet

用小卷积核代替大的卷积核的原理:

用一个5*5的卷积核得到的卷积效果和两个3*3的卷积核的卷积效果是相同的。
Vgg13模型

代码表示
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as Fclass VGG13(nn.Module):def __init__(self, num_classes):super(VGG13, self).__init__()# 定义网络结构self.conv1_1 = nn.Conv2d(3, 64, kernel_size=3, padding=1) # 输入3通道,输出64通道,卷积核大小3x3,填充1self.conv1_2 = nn.Conv2d(64, 64, kernel_size=3, padding=1) # 输入64通道,输出64通道,卷积核大小3x3,填充1self.maxpool1 = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层,卷积核大小2x2,步长2self.conv2_1 = nn.Conv2d(64, 128, kernel_size=3, padding=1) # 输入64通道,输出128通道,卷积核大小3x3,填充1self.conv2_2 = nn.Conv2d(128, 128, kernel_size=3, padding=1) # 输入128通道,输出128通道,卷积核大小3x3,填充1self.maxpool2 = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层,卷积核大小2x2,步长2self.conv3_1 = nn.Conv2d(128, 256, kernel_size=3, padding=1) # 输入128通道,输出256通道,卷积核大小3x3,填充1self.conv3_2 = nn.Conv2d(256, 256, kernel_size=3, padding=1) # 输入256通道,输出256通道,卷积核大小3x3,填充1self.conv3_3 = nn.Conv2d(256, 256, kernel_size=3, padding=1) # 输入256通道,输出256通道,卷积核大小3x3,填充1self.maxpool3 = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层,卷积核大小2x2,步长2self.conv4_1 = nn.Conv2d(256, 512, kernel_size=3, padding=1) # 输入256通道,输出512通道,卷积核大小3x3,填充1self.conv4_2 = nn.Conv2d(512, 512, kernel_size=3, padding=1) # 输入512通道,输出512通道,卷积核大小3x3,填充1self.maxpool4 = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层,卷积核大小2x2,步长2self.conv5_1 = nn.Conv2d(512, 512, kernel_size=3, padding=1) # 输入512通道,输出512通道,卷积核大小3x3,填充1self.conv5_2 = nn.Conv2d(512, 512, kernel_size=3, padding=1) # 输入512通道,输出512通道,卷积核大小3x3,填充1self.maxpool5 = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化层,卷积核大小2x2,步长2# 全连接层self.fc1 = nn.Linear(512 * 7 * 7, 4096) # 将卷积后的输出展平后输入到全连接层self.fc2 = nn.Linear(4096, 4096) # 第二个全连接层self.fc3 = nn.Linear(4096, num_classes) # 最后的分类层def forward(self, x):# 第一层卷积x = self.conv1_1(x) # 输入(224, 224, 3) -> 卷积(64, 224, 224)x = F.relu(x) # 激活函数x = self.conv1_2(x) # 输入(64, 224, 224) -> 卷积(64, 224, 224)x = F.relu(x) # 激活函数x = self.maxpool1(x) # 池化 -> 输出(64, 112, 112)# 第二层卷积x = self.conv2_1(x) # 输入(64, 112, 112) -> 卷积(128, 112, 112)x = F.relu(x) # 激活函数x = self.conv2_2(x) # 输入(128, 112, 112) -> 卷积(128, 112, 112)x = F.relu(x) # 激活函数x = self.maxpool2(x) # 池化 -> 输出(128, 56, 56)# 第三层卷积x = self.conv3_1(x) # 输入(128, 56, 56) -> 卷积(256, 56, 56)x = F.relu(x) # 激活函数x = self.conv3_2(x) # 输入(256, 56, 56) -> 卷积(256, 56, 56)x = F.relu(x) # 激活函数x = self.conv3_3(x) # 输入(256, 56, 56) -> 卷积(256, 56, 56)x = F.relu(x) # 激活函数x = self.maxpool3(x) # 池化 -> 输出(256, 28, 28)# 第四层卷积x = self.conv4_1(x) # 输入(256, 28, 28) -> 卷积(512, 28, 28)x = F.relu(x) # 激活函数x = self.conv4_2(x) # 输入(512, 28, 28) -> 卷积(512, 28, 28)x = F.relu(x) # 激活函数x = self.maxpool4(x) # 池化 -> 输出(512, 14, 14)# 第五层卷积x = self.conv5_1(x) # 输入(512, 14, 14) -> 卷积(512, 14, 14)x = F.relu(x) # 激活函数x = self.conv5_2(x) # 输入(512, 14, 14) -> 卷积(512, 14, 14)x = F.relu(x) # 激活函数x = self.maxpool5(x) # 池化 -> 输出(512, 7, 7)# 展平特征图x = x.view(x.size(0), -1) # 将(512, 7, 7)展平成(512 * 7 * 7,)# 第一个全连接层x = self.fc1(x) # 输入到全连接层(4096)x = F.relu(x) # 激活函数# 第二个全连接层x = self.fc2(x) # 输入到全连接层(4096)x = F.relu(x) # 激活函数# 输出层x = self.fc3(x) # 最终输出 (num_classes)return x
ResNet

1*1卷积的特点是不改变特征图大小,但改变特征图的厚度。1*1卷积的作用是降维,减少参数量。比如对于一个256*112*112的特征图,想要直接用3*3的卷积得到256*112*112的特征图需要的参数量是256*256*3*3,可以改为使用64*256*1*1的卷积核先降维然后使用3*3卷积,再通过1*1卷积改变维度,这样做参数量少很多。

由于回传过程中求梯度时,需要将多个导数值相乘,如果每个导数值都小于1,多个相乘趋于0,这就是梯度消失,如多每个导数值都大于1,多个相乘值很大,这就是梯度爆炸。由于Relu导数值为1,因此一定程度上减轻梯度消失,sigmoid在输入很大时趋于0,容易加深梯度消失。

ResNet 的设计通过引入 残差连接来减缓甚至避免梯度消失的问题。通过 跳跃连接(即输入 x 直接传递到输出层)使得 Out = f(x) + x。这个连接让梯度在反向传播时可以直接传递给输入层。这样,梯度不仅依赖于 f(x)(变换部分),还依赖于 x,从而提供了一个直接的梯度路径,避免了在深层网络中梯度消失。
Out = f(x) + x, 输出与输入层数大小维度不同时,可以通过1*1卷积改变维度,通过增大步长改变大小:

ResNet模型

模型代码表示
import torch.nn as nn
import torchvision.models as models# 加载预训练的ResNet18模型
resNet = models.resnet18()print(resNet)class Residual_block(nn.Module): # 定义残差块def __init__(self, input_channels, out_channels, down_sample=False, strides=1):super().__init__()# 第一个卷积层:卷积核大小为3,步长为strides,填充为1self.conv1 = nn.Conv2d(input_channels, out_channels,kernel_size=3, padding=1, stride=strides)# 第二个卷积层:卷积核大小为3,步长为1,填充为1self.conv2 = nn.Conv2d(out_channels, out_channels,kernel_size=3, padding=1, stride= 1)# 如果输入和输出通道不一致,需要额外添加一个1x1的卷积来匹配维度if input_channels != out_channels:self.conv3 = nn.Conv2d(input_channels, out_channels,kernel_size=1, stride=strides)else:self.conv3 = None # 如果输入和输出通道一致,则不需要1x1卷积# 批归一化层,规范化每一层的输出self.bn1 = nn.BatchNorm2d(out_channels)self.bn2 = nn.BatchNorm2d(out_channels)# ReLU激活函数self.relu = nn.ReLU()def forward(self, X):# 第一个卷积操作后激活out = self.relu(self.bn1(self.conv1(X)))# 第二个卷积操作后激活out = self.bn2(self.conv2(out))# 如果有conv3(即输入和输出通道不同),则执行1x1卷积if self.conv3:X = self.conv3(X)# 跳跃连接:将输入X与输出相加out += X# 返回经过ReLU激活后的结果return self.relu(out)class MyResNet18(nn.Module):def __init__(self):super(MyResNet18, self).__init__()self.conv1 = nn.Conv2d(3, 64, 7, 2, 3) # 输入(3, 224, 224) -> 卷积(64, 112, 112)self.bn1 = nn.BatchNorm2d(64)self.pool1 = nn.MaxPool2d(3, stride=2, padding=1) # 输入(64, 112, 112) -> 最大池化(64, 56, 56)self.relu = nn.ReLU()self.layer1 = nn.Sequential(Residual_block(64, 64), # 输入(64, 56, 56) -> 卷积(64, 56, 56)Residual_block(64, 64) # 输入(64, 56, 56) -> 卷积(64, 56, 56))self.layer2 = nn.Sequential(Residual_block(64, 128, strides=2), # 输入(64, 56, 56) -> 卷积(128, 28, 28)Residual_block(128, 128) # 输入(128, 28, 28) -> 卷积(128, 28, 28))self.layer3 = nn.Sequential(Residual_block(128, 256, strides=2), # 输入(128, 28, 28) -> 卷积(256, 14, 14)Residual_block(256, 256) # 输入(256, 14, 14) -> 卷积(256, 14, 14))self.layer4 = nn.Sequential(Residual_block(256, 512, strides=2), # 输入(256, 14, 14) -> 卷积(512, 7, 7)Residual_block(512, 512) # 输入(512, 7, 7) -> 卷积(512, 7, 7))self.flatten = nn.Flatten()self.adv_pool = nn.AdaptiveAvgPool2d(1) # 输入(512, 7, 7) -> 自适应平均池化(512, 1, 1)self.fc = nn.Linear(512, 1000)def forward(self, x):x = self.conv1(x) # 输入(3, 224, 224) -> 卷积(64, 112, 112)x = self.bn1(x)x = self.relu(x)x = self.pool1(x) # 输入(64, 112, 112) -> 最大池化(64, 56, 56)x = self.layer1(x) # 输入(64, 56, 56) -> 卷积(64, 56, 56)x = self.layer2(x) # 输入(64, 56, 56) -> 卷积(128, 28, 28)x = self.layer3(x) # 输入(128, 28, 28) -> 卷积(256, 14, 14)x = self.layer4(x) # 输入(256, 14, 14) -> 卷积(512, 7, 7)x = self.adv_pool(x) # 输入(512, 7, 7) -> 自适应平均池化(512, 1, 1)x = self.flatten(x)x = self.fc(x)return x
神经网络透析
卷积和全连接的关系
卷积是一种参数共享的 “不全连接" 卷积时参数为卷积核,卷积核要被多次重复使用因此是参数共享的,卷积操作中的每个输出单元都只依赖于输入数据的一个局部区域(而不是整个输入)因此为”不全连接“,因此卷积求梯度方法本质上和全连接一样。相比于直接图片展开为向量进行全连接,卷积使得参数量减少了许多,避免了过拟合。

深度学习= 玩特征
在图像任务中会从图像的原始像素开始,通过多个卷积层逐步提取更高层次的特征。

直到最后求出预测值之前,都属于特征提取。
相关文章:
[深度学习]图片分类任务
图片分类任务 文章目录 图片分类任务分类任务回归和分类如何做分类的输出 图片分类卷积神经网络保持特征图大小不变更大的卷积核和更多的卷积核层数特征图怎么变小卷积神经网络中特征图改变卷积到全连接分类任务的LOSS一个基本的分类神经网络 经典神经网络AlexNetVggNetResNet …...
关系图:赋能数据可视化的动态扩展
关系图 关系图是一种用于展示节点之间关系和连接的图表类型。具有高度的可定制性、丰富的交互功能和动画效果,能够展示节点之间的和连接,以及随着数据的变化而呈现的动态效果。 【组件概述】 1.节点和边的可定制性: 关系图提供了丰富的配置…...
k8s存储介绍(三)valume概述与emptydir
目录 一、Kubernetes 中的 Volume 详解 基本概念 Volume 的主要类型(这里简单介绍,后续章节会详细介绍) 1. 本地存储类型 2. 网络存储类型 3. 云提供商存储 4. 特殊用途类型 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) S…...
Nodejs 项目打包部署方式
方式一:PM2 一、准备工作 确保服务器上已安装 Node.js 环境建议使用 PM2 进行进程管理(需要额外安装) 二、部署步骤 1.首先在服务器上安装 PM2(推荐): npm install -g pm22.将项目代码上传到服务器&…...
uv - Getting Started 开始使用 [官方文档翻译]
文章目录 uv亮点安装项目脚本工具Python 版本pip 接口了解更多 入门安装 uv安装方法独立安装程序PyPICargoHomebrewWinGetScoopDockerGitHub 发布 升级 uvShell 自动补全卸载 第一次使用 uv特性Python 版本脚本项目工具pip 接口实用工具 获取帮助帮助菜单查看版本故障排除问题在…...
C++类与对象的的第三个简单的实战练习-3.25笔记
哔哩哔哩C面向对象高级语言程序设计教程(118集全) 简单实战三 创建项目 打开VS,点击创建一个新项目 创建一个空项目 点击下一步 点击工程名称,选择添加 选择新建项 选择C类 取名 点击确定,这时候还需要一个main.cpp …...
CentOS安装sshpass工具-自动化SSH密码认证
sshpass是一个在Linux环境下用于自动化SSH密码认证的工具。 一、功能特点 自动化SSH登录:sshpass允许用户在命令行中直接传递密码,从而无需在SSH连接时手动输入密码。这对于自动化脚本和批处理任务非常有用,因为它可以在非交互式环境下完成…...
k8s中service概述(一)ClusterIP
ClusterIP 是 Kubernetes 中最基础且常用的 Service 类型,主要用于在集群内部提供稳定的网络访问端点。以下是关于 ClusterIP Service 的详细说明: 1. ClusterIP 的核心功能 集群内部访问:ClusterIP 提供一个集群内部的虚拟 IP(VI…...
详解接口的常见请求方式
详解接口的常见请求方式 一、 常见接口请求方式1. GET2. POST3. PUT4. DELETE5. PATCH6. HEAD7. OPTIONS 二、 实现方法1. 前端实现2. 后端实现 三、 作用与主要区别四、 举例讲解1. 创建 Spring Boot 工程2. 添加依赖3. 编写 Controller 实现接口关键点说明 4. 启动与测试5. 总…...
HarmonyOS-ArkUI Grip组件
我们在学习List的时候,已经捎带引入了Grid。讲解如下图所示: 也就是,如果一个表,长宽基本都是一致的,那么此时可以完全不用Grid也可以实现,并且,优先考虑的就是List。 如果List实现不了的情况下…...
2025清华大学:DeepSeek教程全集(PDF+视频精讲,共10份).zip
一、资料列表 第一课:Deepseek基础入门 第二课:DeepSeek赋能职场 第三课:普通人如何抓住DeepSeek红利 第四课:让科研像聊天一样简单 第五课:DeepSeek与AI幻觉 第六课:基于DeepSeek的AI音乐词曲的创造法 第…...
jupyter使用过程中遇到的问题
1、No module named ‘notebook.extensions’ 报错内容为: No module named notebook.extensions解决办法 出现这个错误代表你尝试给 Jupyter notebook 安装自动补全的插件,但是 notebook 没安装成功; 解决办法:不用 pip 安装 n…...
mac vim命令快捷键
目录 移动光标插入模式复制/粘贴删除搜索/替换退出 移动光标 快捷键说明0 / ^跳到行首,移动到光标所在行的"行首"$跳到行末,移动到光标所在行的"行尾"gg跳到文件第一行G移动到文章的最后[n]G跳到第n行w光标跳到下个字的开头e光标跳…...
【Golang】defer与recover的组合使用
在Go语言中,defer和recover是两个关键特性,通常结合使用以处理资源管理和异常恢复。以下是它们的核心应用场景及使用示例: 1. defer 的应用场景 defer用于延迟执行函数调用,确保在函数退出前执行特定操作。主要用途包括ÿ…...
低代码配置式Web组态解析
低代码配置式Web组态技术通过可视化操作和预置组件库,大幅降低开发门槛,适用于工业控制、物联网监控、数据可视化等场景。以下是综合行业实践和产品特性的分析: 一、核心功能与优势 可视化编辑与拖拽布局 提供图形化编辑器࿰…...
KiLog2MaximumIncrement的由来和KiMaximumIncrementReciprocal的由来
第一部分:KiLog2MaximumIncrement的由来 i 1; j KeMaximumIncrement; while ((1UI64<<i) < KeMaximumIncrement) { i; } KiLog2MaximumIncrement i; 2^17131072 2^18262144 i18KiLog2MaximumIncrement 中…...
基于web的家政服务网站
内容摘要 由于互联网的使用,人们在管理、应用、服务等领域使用数据更加简洁、方便,大大提高了工作效率。互联网正逐渐融入我们的生活,影响和改变我们的生活。 家政服务管理系统是典型的信息管理系统(MIS)。其开发主要…...
mac命令行快捷键
光标移动 Ctrl A: 将光标移动到行首。Ctrl E: 将光标移动到行尾。Option 左箭头: 向左移动一个单词。Option 右箭头: 向右移动一个单词。 删除和修改 Ctrl K: 删除从光标到行尾的所有内容。Ctrl U: 删除从光标到行首的所有内容。Ctrl W: 删除光标前的一个单词。Ctrl …...
聚水潭数据集成到MySQL的最佳实践分享
聚水潭数据集成到MySQL的技术案例分享 在本次技术案例中,我们将探讨如何通过轻易云数据集成平台,将聚水潭的数据高效、可靠地集成到MySQL数据库中。具体的集成方案为“聚水潭-商品信息查询-->BI初本-商品信息表_copy”。该方案旨在实现从聚水潭获取商…...
线性代数核心概念与NumPy科学计算实战全解析
前言 学习方法: 思维导图,梳理 多记忆,函数名和功能,参数 学会应用,不要钻牛角尖 一、浅解线性代数 1.1标量 标量是一个只有大小没有方向的量。在数学上,标量通常表示为一个普通的数字,如质量…...
Spring Boot中接口数据字段为 Long 类型时,前端number精度丢失问题解决方案
Spring Boot中接口数据字段为 Long 类型时,前端number精度丢失问题解决方案 在Spring Boot中,当接口数据字段为 Long 类型时,返回页面的JSON中该字段通常会被序列化为数字类型。 例如,一个Java对象中有一个 Long 类型的属性 id …...
C#自定义曲线便器功能实现(简化版)
目录 一、曲线编辑器实现功能 二、实现方法说明 三、关键代码说明 1、绘制背景板和曲线 2、绘制坐标系面板 3、绘制曲线 四、工程下载连接 一、曲线编辑器实现功能 添加或者删除控制点,通过移动控制点来修改曲线形状 二、实现方法说明 1、坐标系系统&#x…...
Unity Shader编程】之复杂光照
在Unity Shader的LightMode标签中,除了前向渲染和延迟渲染外,还支持多种渲染模式设置。以下是主要分类及用途: 一、核心渲染路径模式 前向渲染相关 ForwardBase 用于基础光照计算,处理环境光、主平行光、逐顶点/SH光源及光照贴图。…...
解锁U盘属性0字节困境,重获数据生机
在数字化浪潮中,U盘宛如一位忠诚的“数据信使”,频繁穿梭于各种设备之间,为我们存储和传输着重要信息。然而,当U盘突然显示属性为0字节时,就如同这位信使突然“失声”,让我们陷入了数据丢失的恐慌之中。U盘…...
⭐算法OJ⭐二叉树的直径【树】(C++实现)Binary Tree Paths
543. Binary Tree Paths(二叉树的直径) Given the root of a binary tree, return the length of the diameter of the tree. The diameter of a binary tree is the length of the longest path between any two nodes in a tree. This path may or m…...
docker使用命令笔记
docker使用命令笔记 1. 安装docker2. 拉取镜像3. 镜像与容器4. 基于镜像创建容器4. 操作创建好的容器5. docker文件传输6. ubuntu的docker的一些基本环境搭建 记录docker的一些使用命令 1. 安装docker 遵循官方安装说明即可,windows需要下载docker desktop后在doc…...
字典树与01trie
字典树简介 当我们通过字典查一个字或单词的时候,我们会通过前缀或关键字的来快速定位一个字的位置,进行快速查找。 字典树就是类似字典中索引表的一种数据结构,能够帮助我们快速定位一个字符串的位置。 字典树是一种存储字符串的数据结构…...
vue - [Vue warn]: Duplicate keys detected: ‘0‘. This may cause an update error.
问题描述: vue项目中,对表单数组赋值时,控制台抛出警告: 问题代码: 问题分析: 1、Vue 要求每个虚拟 DOM 节点必须有唯一的 key。该警告信息通常出现在使用v-for循环的场景中,多个同级节点使用…...
mysql中show命令的使用
在 MySQL 中,SHOW 命令是一个非常实用的工具,用于查询数据库元数据(如数据库、表、列、索引等信息)。以下是常见的 SHOW 命令及其用法: 1. 显示所有数据库 SHOW DATABASES;列出服务器上的所有数据库。 2. 显示当前数据…...
各类神经网络学习:(三)RNN 循环神经网络(中集),同步多对多结构的详细解释
上一篇下一篇RNN(上集)RNN(下集) 同步多对多结构 1)结构详解 ①图解: ②参数含义: x t x_t xt :表示每一个时刻的输入; o t o_t ot :表示每一个时刻的输…...
