【PyTorch】卷积神经网络

文章目录

  • 1. 理论介绍
    • 1.1. 从全连接层到卷积层
      • 1.1.1. 背景
      • 1.1.2. 从全连接层推导出卷积层
    • 1.2. 卷积层
      • 1.2.1. 图像卷积
      • 1.2.2. 填充和步幅
      • 1.2.3. 多通道
    • 1.3. 池化层(又称汇聚层)
      • 1.3.1. 背景
      • 1.3.2. 池化运算
      • 1.3.3. 填充和步幅
      • 1.3.4. 多通道
    • 1.4. 卷积神经网络(LeNet)
      • 1.4.1. 简介
      • 1.4.2. 组成
  • 2. 实例解析
    • 2.1. 实例描述
    • 2.2. 代码实现
      • 2.2.1. 图像中目标的边缘检测
        • 2.2.1.1. 主要代码
        • 2.2.1.2. 完整代码
        • 2.2.1.3. 输出结果
      • 2.2.2. 在FashionMNIST数据集上训练LeNet
        • 2.2.2.1. 主要代码
        • 2.2.2.2. 完整代码
        • 2.2.2.3. 输出结果

1. 理论介绍

1.1. 从全连接层到卷积层

1.1.1. 背景

  • 使用多层感知机学习图像数据面临参数量巨大、数据量要求高的问题,因此这种缺少结构的网络可能会变得不实用。
  • 利用相近像素之间的相互关联性,可以从图像数据中学习得到有效的模型。
  • 设计适合于计算机视觉的神经网络架构的原则
    • 平移不变性:不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应。
    • 局部性:神经网络的前面几层应该只探索输入图像中的局部区域,而不过度在意图像中相隔较远区域的关系。

1.1.2. 从全连接层推导出卷积层

  • 假设多层感知机的输入是二维图像 X \mathbf{X} X,隐藏表示为二维张量 H \mathbf{H} H,且 X \mathbf{X} X H \mathbf{H} H具有相同的形状, [ X ] i , j [\mathbf{X}]_{i,j} [X]i,j [ H ] i , j [\mathbf{H}]_{i,j} [H]i,j分别表示输入图像和隐藏表示中位置 ( i , j ) (i,j) (i,j)处的像素。为了使每个隐藏神经元都能接收到每个输入像素的信息,我们将参数从权重矩阵替换为四维权重张量 W \mathbf{W} W,偏置为 U \mathbf{U} U,则全连接层可表示为
    [ H ] i , j = [ U ] i , j + ∑ k ∑ l [ W ] i , j , k , l [ X ] k , l = [ U ] i , j + ∑ a ∑ b [ V ] i , j , a , b [ X ] i + a , j + b \begin{aligned} [\mathbf{H}]_{i,j} &=[\mathbf{U}]_{i,j}+\sum_k\sum_l{[\mathbf{W}]_{i,j,k,l}}{[\mathbf{X}]_{k,l}} \\ &=[\mathbf{U}]_{i,j}+\sum_a\sum_b{[\mathbf{V}]_{i,j,a,b}}{[\mathbf{X}]_{i+a,j+b}} \end{aligned} [H]i,j=[U]i,j+kl[W]i,j,k,l[X]k,l=[U]i,j+ab[V]i,j,a,b[X]i+a,j+b
    其中, [ V ] i , j , a , b = [ W ] i , j , i + a , j + b [\mathbf{V}]_{i,j,a,b}=[\mathbf{W}]_{i,j,i+a,j+b} [V]i,j,a,b=[W]i,j,i+a,j+b,索引 a a a b b b通过在正偏移和负偏移之间移动覆盖了整个图像。
  • 平移不变性意味着检测对象在输入 X \mathbf{X} X中的平移,应该仅导致隐藏表示 H \mathbf{H} H中的平移,即 U \mathbf{U} U V \mathbf{V} V不依赖与 ( i , j ) (i,j) (i,j)的值,则有
    { [ V ] i , j , a , b = [ V ] a , b [ U ] i , j = u \begin{cases} [\mathbf{V}]_{i,j,a,b} &= [\mathbf{V}]_{a,b}\\ [\mathbf{U}]_{i,j}&=u \end{cases} {[V]i,j,a,b[U]i,j=[V]a,b=u
    因而我们可以简化 H \mathbf{H} H的定义为
    [ H ] i , j = u + ∑ a ∑ b [ V ] a , b [ X ] i + a , j + b [\mathbf{H}]_{i,j} = u + \sum_a\sum_b{[\mathbf{V}]_{a,b}}{[\mathbf{X}]_{i+a,j+b}} [H]i,j=u+ab[V]a,b[X]i+a,j+b
  • 局部性意味着当 ∣ a ∣ > Δ |a|>\Delta a>Δ ∣ b ∣ > Δ |b|>\Delta b>Δ时, [ V ] a , b = 0 [\mathbf{V}]_{a,b}=0 [V]a,b=0,因而我们可以得到
    [ H ] i , j = u + ∑ a = − Δ Δ ∑ b = − Δ Δ [ V ] a , b [ X ] i + a , j + b [\mathbf{H}]_{i,j} = u + \sum_{a=-\Delta}^{\Delta}\sum_{b=-\Delta}^{\Delta}{[\mathbf{V}]_{a,b}}{[\mathbf{X}]_{i+a,j+b}} [H]i,j=u+a=ΔΔb=ΔΔ[V]a,b[X]i+a,j+b
    上式就是一个卷积层,其中 V \mathbf{V} V被称为卷积核或卷积层的权重,而卷积神经网络是包含卷积层的一类特殊的神经网络。
  • 卷积神经网络相较于多层感知机,参数大幅减小,但代价是图像特征要求是平移不变的,并且当确定每个隐藏神经元激活值时,每一层只包含局部的信息

1.2. 卷积层

1.2.1. 图像卷积

  • 卷积运算
    • 数学定义: ( f ∗ g ) ( x ) = ∫ f ( z ) g ( x − z ) d z (f * g)(\mathbf{x}) = \int f(\mathbf{z}) g(\mathbf{x}-\mathbf{z}) d\mathbf{z} (fg)(x)=f(z)g(xz)dz
    • 针对一维离散对象: ( f ∗ g ) ( i ) = ∑ a f ( a ) g ( i − a ) (f * g)(i) = \sum_a f(a) g(i-a) (fg)(i)=af(a)g(ia)
    • 针对二维离散对象: ( f ∗ g ) ( i , j ) = ∑ a ∑ b f ( a , b ) g ( i − a , j − b ) (f * g)(i, j) = \sum_a\sum_b f(a, b) g(i-a, j-b) (fg)(i,j)=abf(a,b)g(ia,jb)
  • 上述卷积层所描述的运算使用 ( i + a , j + b ) (i+a,j+b) (i+a,j+b),称为互相关运算,但这种与卷积运算使用 ( i − a , j − b ) (i-a,j-b) (ia,jb)的实质是一致的,因为我们总是可以匹配两种运算之间的符号。
  • 在卷积层中,输入张量和核张量通过互相关运算产生输出张量。具体过程是卷积核窗口从输入张量的左上角开始,从左到右、从上到下滑动。 当卷积核窗口滑动到新一个位置时,包含在该窗口中的部分张量与卷积核张量进行按元素相乘,得到的张量再求和得到一个单一的标量值,由此我们得出了这一位置的输出张量值。
  • 卷积层中的两个被训练的参数是卷积核权重和标量偏置
  • 要执行严格卷积运算,我们只需水平和垂直翻转二维卷积核张量,然后对输入张量执行互相关运算。由于卷积核是从数据中学习到的,因此无论这些层执行严格的卷积运算还是互相关运算,卷积层的输出都不会受到影响,以后不加区别统称卷积运算
  • 由于输入图像是三维的,对于每一个空间位置,我们想要采用一组而不是一个隐藏表示。这样一组隐藏表示可以想象成一些互相堆叠的二维网格。 因此,我们可以把隐藏表示想象为一系列具有二维张量的通道(channel)。这些通道有时也被称为特征映射(feature maps),因为每个通道都向后续层提供一组空间化的学习特征,可以被视为输入映射到下一层的空间维度的转换器。
  • 在卷积神经网络中,对于某一层的任意元素 x x x,其感受野(receptive field)是指在前向传播期间可能影响 x x x计算的所有元素(来自所有先前层)。当需要检测输入特征中更广区域时,我们可以构建一个更深的卷积网络。

1.2.2. 填充和步幅

  • 卷积的输出形状取决于输入形状和卷积核的形状。
  • 在应用多层卷积时,我们常常丢失边缘像素。解决这个问题的简单方法即为填充(padding),即在输入图像的边界填充元素(通常填充元素是0)。
    • 一般在顶部和底部填充相同数量的行,在左侧和右侧填充相同数量的列
    • 输入 n h × n w n_h\times n_w nh×nw,卷积核 k h × k w k_h\times k_w kh×kw,上下分别填充 p h p_h ph行,左右分别填充 p w p_w pw列,则输出 ( n h − k h + 1 + 2 ∗ p h ) × ( n w − k w + 1 + 2 ∗ p w ) (n_h-k_h+1+2*p_h)\times(n_w-k_w+1+2*p_w) (nhkh+1+2ph)×(nwkw+1+2pw)
    • 在许多情况下,我们需要设置 p h = ( k h − 1 ) / 2 p_h=(k_h-1)/2 ph=(kh1)/2 p w = ( k w − 1 ) / 2 p_w=(k_w-1)/2 pw=(kw1)/2,使输入和输出具有相同的高度和宽度,这样可以在构建网络时更容易地预测每个图层的输出形状。
    • 当卷积核的高度和宽度不同时,我们可以填充不同的高度和宽度,使输出和输入具有相同的高度和宽度。
  • 有时候为了高效计算或是缩减采样次数,卷积窗口可以跳过中间位置,每次滑动多个元素,每次滑动元素的数量称为步幅(stride)。
    • 输入 n h × n w n_h\times n_w nh×nw,卷积核 k h × k w k_h\times k_w kh×kw,上下分别填充 p h p_h ph行,左右分别填充 p w p_w pw列,垂直步幅为 s h s_h sh,水平步幅为 s w s_w sw,则输出 ⌊ ( n h − k h + 2 ∗ p h + s h ) / s h ⌋ × ⌊ ( n w − k w + 2 ∗ p w + s w ) / s w ⌋ \lfloor(n_h-k_h+2*p_h+s_h)/s_h\rfloor\times\lfloor(n_w-k_w+2*p_w+s_w)/s_w\rfloor ⌊(nhkh+2ph+sh)/sh×⌊(nwkw+2pw+sw)/sw如果我们设置了 p h = ( k h − 1 ) / 2 p_h=(k_h-1)/2 ph=(kh1)/2 p w = ( k w − 1 ) / 2 p_w=(k_w-1)/2 pw=(kw1)/2,则输出 ⌊ ( n h + s h − 1 ) / s h ⌋ × ⌊ ( n w + s w − 1 ) / s w ⌋ \lfloor(n_h+s_h-1)/s_h\rfloor\times\lfloor(n_w+s_w-1)/s_w\rfloor ⌊(nh+sh1)/sh×⌊(nw+sw1)/sw如果进一步,输入的高度和宽度可以被垂直和水平步幅整除,则输出 ( n h / s h ) × ( n w / s w ) (n_h/s_h)\times(n_w/s_w) (nh/sh)×(nw/sw)
  • 为了简洁起见,输入高度上下两侧分别为 p h p_h ph,输入宽度左右两侧的填充数量分别为 p w p_w pw时,称为填充 ( p h , p w ) (p_h,p_w) (ph,pw) p h = p w = p p_h=p_w=p ph=pw=p时,填充是 p p p;同理,高度和宽度上的步幅分别为 s h s_h sh s w s_w sw时,称为步幅 ( s h , s w ) (s_h,s_w) (sh,sw) s h = s w = s s_h=s_w=s sh=sw=s时,步幅是 s s s。默认情况下,填充为0,步幅为1。在实践中,我们很少使用不一致的步幅或填充,即总有 p h = p w p_h=p_w ph=pw s h = s w s_h=s_w sh=sw

1.2.3. 多通道

  • 多输入通道
    • 假设输入的通道数为 c i c_i ci,那么卷积核的输入通道数也需要为 c i c_i ci,如果单通道卷积核的窗口形状是 k h × k w k_h\times k_w kh×kw,那么通道数为 c i c_i ci的卷积核的窗口形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw
    • 多通道输入和多输入通道卷积核之间进行二维互相关运算可以对每个通道输入的二维张量和卷积核的二维张量进行互相关运算,再对通道求和得到二维张量。
  • 多输出通道
    • 可以将每个通道看作对不同特征的响应,但多输出通道并不仅是学习多个单通道的检测器,因为每个通道不是独立学习的,而是为了共同使用而优化的。
    • 假设 c i , c o c_i,c_o ci,co分别为输入输出通道数, k h , k w k_h,k_w kh,kw分别为卷积核的高度和宽度,则为了获得多个通道的输出,我们可以为每个输出通道创建一个形状为 c i × k h × k w c_i\times k_h\times k_w ci×kh×kw的卷积核张量,这样卷积核的形状为 c o × c i × k h × k w c_o\times c_i\times k_h\times k_w co×ci×kh×kw。在互相关运算中,每个输出通道先获取所有输入通道,再以对应该输出通道的卷积核计算出结果。
  • 1 × 1 1\times1 1×1卷积层
    • 失去了卷积层的特有能力——在高度和宽度维度上,识别相邻元素间相互作用的能力。
    • 唯一计算发生在通道上。输出中的每个元素都是从输入图像中同一位置的元素的线性组合。 可以将 1 × 1 1\times1 1×1卷积看作在每个像素位置应用的全连接层 1 × 1 1\times1 1×1卷积层的权重维度为 c i × c o c_i\times c_o ci×co,再额外加上一个偏置。
    • 通常用于调整网络层的通道数量和控制模型复杂性。

1.3. 池化层(又称汇聚层)

1.3.1. 背景

  • 机器学习任务通常会跟全局图像的问题有关,所以我们最后一层的神经元应该对整个输入的全局敏感。通过逐渐聚合信息,生成越来越粗糙的映射,最终实现学习全局表示的目标,同时将卷积图层的所有优势保留在中间层。
  • 池化层的主要作用是降低卷积层对位置的敏感性,同时降低对空间降采样表示的敏感性。

1.3.2. 池化运算

  • 与卷积运算类似, p × q p\times q p×q池化运算使用一个固定形状为 p × q p\times q p×q池化窗口,根据其步幅大小在输入的所有区域上滑动计算相应输出。
  • 池化运算是确定性的,我们通常计算池化窗口中所有元素的最大值或平均值。这些操作分别称为最大池化(maximum pooling)和平均池化(average pooling)。

1.3.3. 填充和步幅

池化层的填充和步幅与卷积层类似。默认情况下,深度学习框架中的步幅与池化窗口的形状大小相同。

1.3.4. 多通道

在处理多通道输入数据时,池化层在每个输入通道上单独运算,而不是像卷积层一样在通道上对输入进行汇总。 这意味着池化层的输出通道数与输入通道数相同

1.4. 卷积神经网络(LeNet)

1.4.1. 简介

  • LeNet是最早发布的卷积神经网络之一,由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的(并以其命名),目的是识别图像中的手写数字。
  • Yann LeCun发表了第一篇通过反向传播成功训练卷积神经网络的研究,这项工作代表了十多年来神经网络研究开发的成果。
  • LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。

1.4.2. 组成

  • 总体来看,LeNet(LeNet-5)由两个部分组成:
    • 卷积编码器:由两个卷积层组成;
    • 全连接层密集块:由三个全连接层组成。
      LeNet
  • 数据维度变化
    卷积层数据维度表示:(样本数,通道数,高度,宽度);
    展平层、全连接层输出数据维度表示:(样本数,输出数)。
    位置数据维度
    输入数据28 x 28
    (C1) 5 × 5 5\times5 5×5卷积层(填充2)输入1 x 1 x 28 x 28
    (C1) 5 × 5 5\times5 5×5卷积层(填充2)输出1 x 6 x 28 x 28
    Sigmoid激活函数输出1 x 6 x 28 x 28
    (S2) 2 × 2 2\times2 2×2平均池化层(步幅2)输出1 x 6 x 14 x 14
    (C3) 5 × 5 5\times5 5×5卷积层输入1 x 6 x 10 x 10
    (C3) 5 × 5 5\times5 5×5卷积层输出1 x 16 x 10 x 10
    Sigmoid激活函数输出1 x 16 x 10 x 10
    (S4) 2 × 2 2\times2 2×2平均池化层输出1 x 16 x 5 x 5
    展平层输出1 x 400
    (120-F5)全连接层输出1 x 120
    Sigmoid激活函数输出1 x 120
    (84-F6)全连接层输出1 x 84
    Sigmoid激活函数输出1 x 84
    输出结果1 x 10

2. 实例解析

2.1. 实例描述

  • 图像中目标的边缘检测
    构造一个 6 × 8 6\times8 6×8像素的黑白图像,中间四列为黑色(0),其余像素为白色(1)。要求进行边缘检测,输出中的1代表从白色到黑色的边缘,-1代表从黑色到白色的边缘,其他情况的输出为0。
  • 在FashionMNIST数据集上训练LeNet

2.2. 代码实现

2.2.1. 图像中目标的边缘检测

2.2.1.1. 主要代码
net = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False)
2.2.1.2. 完整代码
import torch
from torch import nn
from torch.nn import functional as Fif __name__ == '__main__':# 全局参数设置lr = 3e-2num_epochs = 10# 生成数据集X = torch.ones(6, 8)X[:, 2:6] = 0Y = torch.zeros(6, 7)Y[:, 1], Y[:, 5] = 1, -1# 批数、通道数、高度、宽度X = X.reshape(1, 1, 6, 8)Y = Y.reshape(1, 1, 6, 7)print('1. 使用边缘检测器(1, -1)')K = torch.tensor([1.0, -1.0]).reshape(1, 1, 1, 2)O = F.conv2d(X, K, bias=None)print(f'边缘检测器的结果O与标准结果Y是否相同:{O.equal(Y)}')print('2. 学习卷积核')net = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False)# 训练循环for epoch in range(num_epochs):loss = (net(X) - Y) ** 2    # 平方误差net.zero_grad()loss.sum().backward()net.weight.data[:] -= lr * net.weight.grad  # 参数更新print(f'epoch {epoch + 1}, loss {loss.sum():.3f}')print(f'卷积核权重:{net.weight.data.reshape(1, 2)}')
2.2.1.3. 输出结果
1. 使用边缘检测器(1, -1)
边缘检测器的结果O与标准结果Y是否相同:True
2. 学习卷积核
epoch 1, loss 10.272
epoch 2, loss 4.320
epoch 3, loss 1.842
epoch 4, loss 0.801
epoch 5, loss 0.358
epoch 6, loss 0.165
epoch 7, loss 0.080
epoch 8, loss 0.040
epoch 9, loss 0.022
epoch 10, loss 0.012
卷积核权重:tensor([[ 0.9799, -0.9993]])

2.2.2. 在FashionMNIST数据集上训练LeNet

2.2.2.1. 主要代码
LeNet = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Flatten(),nn.Linear(400, 120), nn.Sigmoid(),nn.Linear(120, 84), nn.Sigmoid(),nn.Linear(84, 10)
).to(device)
2.2.2.2. 完整代码
import torch, os
from torch import nn, optim
from torch.utils.data import DataLoader
from torchvision.transforms import Compose, ToTensor, Resize
from torchvision.datasets import FashionMNIST
from tensorboardX import SummaryWriter
from rich.progress import trackdef load_dataset():"""加载数据集"""root = "./dataset"transform = Compose([ToTensor()])mnist_train = FashionMNIST(root, True, transform, download=True)mnist_test = FashionMNIST(root, False, transform, download=True)dataloader_train = DataLoader(mnist_train, batch_size, shuffle=True, num_workers=num_workers,)dataloader_test = DataLoader(mnist_test, batch_size, shuffle=False,num_workers=num_workers,)return dataloader_train, dataloader_testif __name__ == "__main__":# 全局参数设置num_epochs = 100batch_size = 256num_workers = 3lr = 0.9device = torch.device('cuda')# 创建记录器def log_dir():root = "runs"if not os.path.exists(root):os.mkdir(root)order = len(os.listdir(root)) + 1return f'{root}/exp{order}'writer = SummaryWriter(log_dir=log_dir())# 数据集配置dataloader_train, dataloader_test = load_dataset()# 模型配置LeNet = nn.Sequential(nn.Conv2d(1, 6, kernel_size=5, padding=2), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Conv2d(6, 16, kernel_size=5), nn.Sigmoid(),nn.AvgPool2d(kernel_size=2, stride=2),nn.Flatten(),nn.Linear(400, 120), nn.Sigmoid(),nn.Linear(120, 84), nn.Sigmoid(),nn.Linear(84, 10)).to(device)def init_weights(m):if type(m) == nn.Linear or type(m) == nn.Conv2d:nn.init.xavier_uniform_(m.weight)LeNet.apply(init_weights)criterion = nn.CrossEntropyLoss(reduction='none')optimizer = optim.SGD(LeNet.parameters(), lr=lr)# 训练循环for epoch in track(range(num_epochs), description='LeNet'):LeNet.train()for X, y in dataloader_train:X, y = X.to(device), y.to(device)optimizer.zero_grad()loss = criterion(LeNet(X), y)loss.mean().backward()optimizer.step()LeNet.eval()with torch.no_grad():train_loss, train_acc, num_samples = 0.0, 0.0, 0for X, y in dataloader_train:X, y = X.to(device), y.to(device)y_hat = LeNet(X)loss = criterion(y_hat, y)train_loss += loss.sum()train_acc += (y_hat.argmax(dim=1) == y).sum()num_samples += y.numel()train_loss /= num_samplestrain_acc /= num_samplestest_acc, num_samples = 0.0, 0for X, y in dataloader_test:X, y = X.to(device), y.to(device)y_hat = LeNet(X)test_acc += (y_hat.argmax(dim=1) == y).sum()num_samples += y.numel()test_acc /= num_sampleswriter.add_scalars('metrics', {'train_loss': train_loss,'train_acc': train_acc,'test_acc': test_acc}, epoch)writer.close()
2.2.2.3. 输出结果

lenet

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

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

相关文章

抖店商品卡流量怎么做?附实操玩法解析及案例分享!

我是电商珠珠 在抖店中,我们会看到一些店铺从来都不直播,官方账号的作品很少,但是他的店铺GMV依旧过万。 其实这就是抖音的货架电商所带来的优势,抖音现在主要由两部分组成,一部分是内容场,就是咱们平时刷…

SpringBoot整合MongoDB详解

SpringBoot——整合MongoDB详解 注意: MongoDB默认是本机访问 需要开启远程访问 window修改如下 linux 修改如下 /usr/local/mongodb4/mongodb.conf 详细内容 dbpath/data/mongodb4/mongo #数据文件保存地址 logpath/data/mongodb4/log/mongod.log #日志保存地址…

架构设计系列之基础:初探软件架构设计

11 月开始突发奇想,想把自己在公司内部做的技术培训、平时的技术总结等等的内容分享出来,于是就开通了一个 Wechat 订阅号(灸哥漫谈),开始同步发送内容。 今天(12 月 10 日)也同步在 CSDN 上开通…

mysql字段设计规范:使用unsigned(无符号的)存储非负值

如果一个字段存储的是数值,并且是非负数,要设置为unsigned(无符号的)。 例如: 备注:对于类型是 FLOAT、 DOUBLE和 DECIMAL的,UNSIGNED属性已经废弃了,可能在mysql的未来某个版本去…

AI隆重软件,AI原创文章隆重软件

随着信息量的急剧增加,许多写作者、网站管理员和内容创作者们纷纷感受到了文章降重的压力。原始文本的降重,需要保留关键信息的同时避免重复,这是一项既繁琐又耗时的任务。 改写软件的批量降重功能 147SEO改写软件在降重领域的卓越表现主要体…

常见SSL证书都有哪些格式

常见Web服务软件 常见的Web服务软件,通常都基于OpenSSL和Java两种基础密码库。 Tomcat、Weblogic、JBoss等Web服务软件,一般使用Java提供的密码库。通过Java Development Kit(JDK)工具包中的Keytool工具,生成Java Ke…

android studio 按键点击事件的实现方法

一、onClick属性&#xff1a; 1&#xff09;、在activity_main.xml中设置button的onClick属性&#xff1a; <Buttonandroid:id"id/button"android:layout_width"wrap_content"android:layout_height"wrap_content"android:text"开灯&q…

SpringCloud系列(三)| 集成Nacos注册中心

好了万事具备&#xff0c;接下来我们就开始搭建我们SpringCloud项目。本章节我们先来搭建我们SpringCloud项目框架&#xff0c;然后开发两个服务&#xff0c;把他们注册到Nacos注册中心中。上一章节我们已经介绍了Nacos是可以作为注册中心和配置中心的&#xff0c;这一章节我们…

Linux centos8安装JDK1.8、tomcat

一、安装jdk 1.如果之前安装过jdk&#xff0c;先卸载掉旧的 rpm -qa | grep -i jdk 2.检查yum中有没有java1.8的包 yum list java-1.8* 3.yum安装jdk yum install java-1.8.0-openjdk* -y 4.验证 二、安装tomcat Index of /tomcat 可以在这里选择你想要安装的tomcat版本…

【unity实战】一个通用的FPS枪支不同武器射击控制脚本

文章目录 前言模型素材文章用到的粒子火光特效射击效果换弹瞄准开枪抖动效果设置显示文本最终代码不同武器射击效果1. 手枪2. 机枪3. 狙击枪4. 霰弹枪5. 加特林 其他感谢完结 前言 实现FPS枪支不同武器效果&#xff0c;比如手枪&#xff0c;喷子&#xff0c;狙击枪&#xff0c…

ISCTF2023 Reverse方向 WP

文章目录 ReversecrackmeEasyRebabyReeasy_z3FloweyRSAeasy_flower_teamfx_rez3_revengeWHERE Reverse crackme 、 加了UPX壳&#xff0c;可以看到EP Section处UPX标识被修改了 用WinHex修改 之后UPX脱壳 得到flag。 EasyRe 逆向一下&#xff0c;先逆序&#xff0c;再做一些…

机器人行业数据闭环实践:从对象存储到 JuiceFS

JuiceFS 社区聚集了来自各行各业的前沿科技用户。本次分享的案例来源于刻行&#xff0c;一家商用服务机器人领域科技企业。 商用服务机器人指的是我们日常生活中常见的清洁机器人、送餐机器人、仓库机器人等。刻行采用 JuiceFS 来弥补对象存储性能不足等问题。 值得一提的是&am…

godot 报错Unable to initialize Vulkan video driver解决

版本 godot 4.2.1 现象 godot4.2.1 默认使用vulkan驱动&#xff0c;如果再不支持vulkan驱动的主机上&#xff0c;进入引擎编辑器将报错如下 解决 启动参数添加 –rendering-driver opengl3 即可进入引擎编辑器 此时运行项目仍然会报错无法初始化驱动 在项目设置中配置编…

编译Sqlite3记录

下载源文件&#xff1a; 下载地址&#xff1a;SQLite Download Page 打开QtCreator创建新的工程&#xff0c;选择纯C工程&#xff0c;将main.c删除&#xff0c;将下载的源码解压后的文件复制到并添加到工程中&#xff0c;其中的文件包括&#xff1a;sqlite3ext.h、sqlite3.h、…

Android多进程和跨进程通讯方式

前言 我们经常开发过程中经常会听到线程和进程&#xff0c;在讲述Android进程多进程前我打算先简单梳理一下这俩者。 了解什么是进程与线程 进程&#xff1a; 系统中正在运行的一个应用程序&#xff0c;某个程序一旦运行就是一个进程&#xff0c;是资源分配的最小单位&#…

自动化测试 (二) Web自动化测试原理

目前市面上有很多Web UI自动化测试框架&#xff0c;比如WatiN, Selinimu,WebDriver&#xff0c;还有VS2010中的Coded UI等等. 这些框架都可以操作Web中的控件&#xff0c;模拟用户输入&#xff0c;点击等操作&#xff0c;实现Web自动化测试。其实这些工具的原理都一样&#xf…

element-ui样式(一)

1.去掉表格横线 HTML表格标签&#xff1a; table&#xff1a;定义表格&#xff0c;生成的表格在一对<table></table>中&#xff1b; <th>&#xff1a;定义表格的表头&#xff0c;一般是表头中的内容会被加黑&#xff08;table head&#xff09;&#xff1b;…

MySQL 系列:注意 ORDER 和 LIMIT 联合使用的陷阱

文章目录 前言背后的原因ORDER BY 排序列存在相同值时返回顺序是不固定的LIMIT 和 ORDER BY 联合使用时的行为ORDER BY 或 GROUP BY 和 LIMIT 联合使用优化器默认使用有序索引 如何解决其它说明个人简介 前言 不知道大家在在分页查询中有没有遇到过这个问题&#xff0c;分页查…

通俗易懂:插入排序算法全解析(C++)

插入排序算法是一种简单直观的排序算法&#xff0c;它的原理就像我们玩扑克牌时整理手中的牌一样。下面我将用通俗易懂的方式来解释插入排序算法的工作原理。 假设我们手上有一副无序的扑克牌&#xff0c;我们的目标是将它们从小到大排列起来。插入排序算法的思想是&#xff0…

0基础学习VR全景平台篇第127篇:什么是VR全景/720全景漫游?

“全景”作为一种表现宽阔视野的手法&#xff0c;在很久之前就得到了普遍的认同。北宋年间&#xff0c;由张择端绘制的《清明上河图》就是一幅著名的全景画。摄影术出现后&#xff0c;全景摄影也随之而生。 到今天&#xff0c;全景拍摄不再被专业摄影师所独享&#xff0c;广大…