PyTorch深度学习实战(2)——PyTorch快速入门

news2024/11/15 9:20:55

PyTorch的简洁设计使得它易于入门,在深入介绍PyTorch之前,本文先介绍一些PyTorch的基础知识,以便读者能够对PyTorch有一个大致的了解,并能够用PyTorch搭建一个简单的神经网络。

1 Tensor

Tensor是PyTorch中最重要的数据结构,它可以是一个数(标量)、一维数组(向量)、二维数组(如矩阵、黑白图片等)或者更高维的数组(如彩色图片、视频等)。Tensor与NumPy的ndarrays类似,但Tensor可以使用GPU加速。下面通过几个示例了解Tensor的基本使用方法:

In: import torch as t
    t.__version__ # 查看pytorch的版本信息Out:'1.8.0'In: # 构建一个2×3的矩阵,只分配了空间未初始化,其数值取决于内存空间的状态
    x = t.Tensor(2, 3) # 维度:2×3
    xOut:tensor([[7.9668e-37, 4.5904e-41, 7.9668e-37],
        	[4.5904e-41, 0.0000e+00, 0.0000e+00]])

注意:torch.Tensor()可以使用int类型的整数初始化矩阵的行、列数,torch.tensor()需要确切的数据值进行初始化。

In: y = t.Tensor(5)
    print(y.size())
    z = t.tensor([5]) # torch.tensor需要确切数值进行初始化
    print(z.size())
Out:torch.Size([5])
	torch.Size([1])
In: # 使用正态分布初始化二维数组
    x = t.rand(2, 3)  
    x
Out:tensor([[0.1533, 0.9600, 0.5278],
        	[0.5453, 0.3827, 0.3212]])
        
In: print(x.shape) # 查看x的形状
    x.size()[1], x.size(1) # 查看列的个数, 这两种写法等价Out:torch.Size([2, 3])
	(3, 3)
In: y = t.rand(2, 3)
    # 加法的第一种写法
    x + y
Out:tensor([[1.1202, 1.6476, 1.1220],
            [1.0161, 1.1325, 0.3405]])
In: # 加法的第二种写法
    t.add(x, y)
Out:tensor([[1.1202, 1.6476, 1.1220],
            [1.0161, 1.1325, 0.3405]])
In: # 加法的第三种写法:指定加法结果的输出目标为result
    result = t.Tensor(2, 3) # 预先分配空间
    t.add(x, y, out=result) # 输入到result
    result
Out:tensor([[1.1202, 1.6476, 1.1220],
            [1.0161, 1.1325, 0.3405]])
In: print('初始的y值')
    print(y)
    
    print('第一种加法,y的结果')
    y.add(x) # 普通加法,不改变y的值
    print(y)
    
    print('第二种加法,y的结果')
    y.add_(x) # inplace加法,y改变了
    print(y)
Out:初始的y值
    tensor([[0.9669, 0.6877, 0.5942],
            [0.4708, 0.7498, 0.0193]])
第一种加法,y的结果
    tensor([[0.9669, 0.6877, 0.5942],
            [0.4708, 0.7498, 0.0193]])
第二种加法,y的结果
    tensor([[1.1202, 1.6476, 1.1220],
            [1.0161, 1.1325, 0.3405]])

注意:函数名后面带下划线_的函数称为inplace操作,会修改Tensor本身。例如,x.add_(y)x.t_()会改变 xx.add(y)x.t()返回一个新的Tensor,x不变。

In: # Tensor的索引操作与NumPy类似
    x[:, 1]
Out:tensor([0.8969, 0.7502, 0.7583, 0.3251, 0.2864])Tensor和NumPy数组之间的相互操作非常容易且快速。对于Tensor不支持的操作,可以先转为NumPy数组进行处理,之后再转回Tensor。
In: a = t.ones(5) # 新建一个全1的Tensor
    a
Out:tensor([1., 1., 1., 1., 1.])
In: b = a.numpy() # Tensor → NumPy
    b
Out:array([1., 1., 1., 1., 1.], dtype=float32)
In: import numpy as np
    a = np.ones(5)
    b = t.from_numpy(a) # NumPy → Tensor
    print(a)
    print(b) 
Out:[1. 1. 1. 1. 1.]
    tensor([1., 1., 1., 1., 1.], dtype=torch.float64)

因为Tensor和NumPy对象大多数情况下共享内存,所以它们之间的转换很快,几乎不会消耗资源。这也意味着,其中一个发生了变化,另外一个会随之改变。

In: b.add_(1) # 以下划线结尾的函数会修改自身
    print(b)
    print(a)  # Tensor和NumPy共享内存Out:tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
    [2. 2. 2. 2. 2.]

如果想获取Tensor中某一个元素的值,那么可以使用索引操作得到一个零维度的Tensor(一般称为scalar),再通过scalar.item()获取具体数值。

In: scalar = b[0]
    scalar
Out:tensor(2., dtype=torch.float64)
In: scalar.shape # 0-dim 
Out:torch.Size([])
In: scalar.item() # 使用scalar.item()可以从中取出Python对象的数值Out:2.0
In: tensor = t.tensor([2]) # 注意和scalar的区别
    tensor, scalar
Out:(tensor([2]), tensor(2., dtype=torch.float64))
In: tensor.size(), scalar.size()
Out:(torch.Size([1]), torch.Size([]))
In: # 只有一个元素的tensor也可以调用tensor.item()
    tensor.item(), scalar.item()
Out:(2, 2.0)
In: tensor = t.tensor([3,4]) # 新建一个包含3,4两个元素的Tensor
    old_tensor = tensor
    new_tensor = old_tensor.clone()
    new_tensor[0] = 1111
    old_tensor, new_tensor
Out:(tensor([3, 4]), tensor([1111,    4]))

注意:t.tensor()tensor.clone()总是会进行数据拷贝,新的Tensor和原来的数据不再共享内存。如果需要共享内存,那么可以使用torch.from_numpy()或者tensor.detach()新建一个Tensor。

In: new_tensor = old_tensor.detach()
    new_tensor[0] = 1111
    old_tensor, new_tensorOut:(tensor([1111,    4]), tensor([1111,    4]))

在深度学习中,Tensor的维度特征十分重要。有时需要对Tensor的维度进行变换,针对该问题,PyTorch提供了许多快捷的变换方式,例如维度变换viewreshape,维度交换permutetranspose等。

在维度变换中,可以使用view操作与reshape操作来改变Tensor的维度,二者之间有以下区别。

  • view只能用于内存中连续存储的Tensor。如果Tensor使用了transposepermute等维度交换操作,那么Tensor在内存中会变得不连续。此时不能直接使用view操作,应该先将其连续化,即tensor.contiguous.view()
  • reshape操作不要求Tensor在内存中是连续的,直接使用即可。

下面举例说明几种维度变换操作:

In: x = t.randn(4, 4)
    y = x.view(16)
    z = x.view(-1, 8) # -1表示由其他维度计算决定
    print(x.size(), y.size(), z.size())
Out:torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

In: p = x.reshape(-1, 8)
    print(p.shape)
Out:torch.Size([2, 8])

In: x1 = t.randn(2, 4, 6)
    o1 = x1.permute((2, 1, 0))
    o2 = x1.transpose(0, 2)
    print(f'o1 size {o1.size()}')
    print(f'o2 size {o2.size()}')
Out:o1 size torch.Size([6, 4, 2])
	o2 size torch.Size([6, 4, 2])

除了对Tensor进行维度变换,还可以针对Tensor的某些维度进行其他的操作。例如,tensor.squeeze()

可以进行Tensor的维度压缩、tensor.unsqueeze()可以扩展Tensor的维度、torch.cat()可以在Tensor指定维度上进行拼接等。

In: x = t.randn(3, 2, 1, 1)
    y = x.squeeze(-1)    # 将最后一维进行维度压缩
    z = x.unsqueeze(0)   # 在最前面增加一个维度
    w = t.cat((x, x), 0) # 在第一维度连接两个x
    print(f'y size {y.shape}')
    print(f'z size {z.shape}')
    print(f'w size {w.shape}')Out:y size torch.Size([3, 2, 1])
	z size torch.Size([1, 3, 2, 1, 1])
	w size torch.Size([6, 2, 1, 1])

Tensor可以通过.cuda()方法或者.to(device)方法转为GPU的Tensor,从而享受GPU带来的加速运算。

In: # 在不支持CUDA的机器下,下一步还是在CPU上运行
    device = t.device("cuda:0" if t.cuda.is_available() else "cpu")
    x = x.to(device)
    y = y.to(x.device)
    z = x + y

此时,读者可能会发现GPU运算的速度并未提升太多,这是因为x和y的规模太小、运算简单,而且将数据从内存转移到显存需要额外的开销。GPU的优势需要在大规模数据和复杂运算下才能体现出来。

2 autograd:自动微分

在深度学习中,反向传播算法被用来计算梯度,其主要流程为通过梯度下降法来最小化损失函数,以此更新网络参数。PyTorch中的autograd模块实现了自动反向传播的功能,optim模块实现了常见的梯度下降优化方法。几乎所有的Tensor操作,autograd都能为它们提供自动微分,避免手动计算导数的复杂过程。

如果想要使用autograd功能,那么需要对求导的Tensor设置tensor.requries_grad=True,下面举例说明autograd模块的用法:

In: # 为Tensor设置requires_grad标识,代表着需要求导数
    # PyTorch会自动调用autograd对Tensor求导
    x = t.ones(2, 2, requires_grad=True)
    
    # 上一步等价于
    # x = t.ones(2,2)
    # x.requires_grad = True
    xOut:tensor([[1., 1.],
        	[1., 1.]], requires_grad=True)In: y = x.sum()
    yOut:tensor(4., grad_fn=<SumBackward0>)In: y.grad_fn
Out:<SumBackward0 at 0x7fca878c8748>
In: y.backward() # 反向传播,计算梯度In: # y = x.sum() = (x[0][0] + x[0][1] + x[1][0] + x[1][1])
    # 每个值的梯度都为1
    x.grad 
Out:tensor([[1., 1.],
        	[1., 1.]])

注意:grad在反向传播过程中是累加的(accumulated)。也就是说,反向传播得到的梯度会累加之前的梯度。因此,每次在进行反向传播之前需要把梯度清零。

In: y.backward()
    x.gradOut:tensor([[2., 2.],
        	[2., 2.]])In: y.backward()
    x.gradOut:tensor([[3., 3.],
        	[3., 3.]])In: # 以下划线结束的函数是inplace操作,会修改自身的值,如add_
    x.grad.data.zero_()Out:tensor([[0., 0.],
        	[0., 0.]])In: y.backward()
    x.grad # 清零后计算得到正确的梯度值Out:tensor([[1., 1.],
        	[1., 1.]])In: a = t.randn(2, 2)
    a = ((a * 3) / (a - 1))
    print(a.requires_grad)
    a.requires_grad_(True)
    print(a.requires_grad)
    b = (a * a).sum()
    print(b.grad_fn)
Out:False
	True
	<SumBackward0 object at 0x7fca87873128>

3 神经网络

虽然autograd实现了反向传播功能,但是直接用它来写深度学习的代码还是稍显复杂。torch.nn是专门为神经网络设计的模块化接口,它构建于autograd之上,可以用来定义和运行神经网络。nn.Modulenn中最重要的类,它可以看作是一个神经网络的封装,包含神经网络各层的定义以及前向传播(forward)方法,通过forward(input)可以返回前向传播的结果。下面以最早的卷积神经网络LeNet1为例,来看看如何用nn.Module实现该网络结构,LeNet的网络结构如图2-10所示。

[^1]: 
@article{lecun1998gradient,
  title={Gradient-based learning applied to document recognition},
  author={LECUN Y, BOTTOU L, BENGIO Y, et al},
  journal={Proceedings of the IEEE},
  volume={86},
  number={11},
  pages={2278--2324},
  year={1998},
  publisher={Ieee}
}

LeNet共有7层,它的输入图像的大小为$32 \times 32$,共经过2个卷积层、2次下采样操作以及3个全连接层得到最终的10维输出。在实现该网络之前,这里先对神经网络的通用训练步骤进行说明。

(1)定义一个包含可学习参数的神经网络。

(2)加载用于训练该网络的数据集。

(3)进行前向传播得到网络的输出结果,计算损失(网络输出结果与正确结果的差距)。

(4)进行反向传播,更新网络参数。

(5)保存网络模型。

3.1 定义网络

在定义网络时,模型需要继承nn.Module,并实现它的forward方法。其中,网络里含有可学习参数的层应该放在构造函数__init__()中,如果某一层(如ReLU)不含有可学习参数,那么它既可以放在构造函数中,又可以放在forward方法中。这里将这些不含有可学习参数的层放在forward方法中,并使用nn.functional实现:

In: import torch.nn as nn
    import torch.nn.functional as F
    
    class Net(nn.Module):
        def __init__(self):
            # nn.Module子类的函数必须在构造函数中执行父类的构造函数
            # 下式等价于nn.Module.__init__(self)
            super().__init__()
            
            # 卷积层,'1'表示输入图片为单通道, '6'表示输出通道数,'5'表示卷积核为5×5
            self.conv1 = nn.Conv2d(1, 6, 5) 
            # 卷积层,'6'表示输入图片为单通道, '16'表示输出通道数,'5'表示卷积核为5×5
            self.conv2 = nn.Conv2d(6, 16, 5) 
            # 仿射层/全连接层,y = Wx + b
            self.fc1 = nn.Linear(16 * 5 * 5, 120) 
            self.fc2 = nn.Linear(120, 84)
            self.fc3 = nn.Linear(84, 10)
    
        def forward(self, x): 
            # 卷积 -> 激活 -> 池化 
            x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
            x = F.max_pool2d(F.relu(self.conv2(x)), 2) 
            # 改变Tensor的形状,-1表示自适应
            x = x.view(x.size()[0], -1) 
            x = F.relu(self.fc1(x))
            x = F.relu(self.fc2(x))
            x = self.fc3(x)        
            return x
    
    net = Net()
    print(net)
 
 Out:Net(
        (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
        (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
        (fc1): Linear(in_features=400, out_features=120, bias=True)
        (fc2): Linear(in_features=120, out_features=84, bias=True)
        (fc3): Linear(in_features=84, out_features=10, bias=True)
    )

用户只需要在nn.Module的子类中定义了forward函数,backward函数就会自动实现(利用autograd)。在forward函数中不仅可以使用Tensor支持的任何函数,还可以使用if、for、print、log等Python语法,写法和标准的Python写法一致。

使用net.parameters()可以得到网络的可学习参数,使用net.named_parameters()可以同时得到网络的可学习参数及其名称,下面举例说明:

In: params = list(net.parameters())
    print(len(params))
Out:10

In: for name, parameters in net.named_parameters():
        print(name, ':', parameters.size())
Out:conv1.weight : torch.Size([6, 1, 5, 5])
    conv1.bias : torch.Size([6])
    conv2.weight : torch.Size([16, 6, 5, 5])
    conv2.bias : torch.Size([16])
    fc1.weight : torch.Size([120, 400])
    fc1.bias : torch.Size([120])
    fc2.weight : torch.Size([84, 120])
    fc2.bias : torch.Size([84])
    fc3.weight : torch.Size([10, 84])
    fc3.bias : torch.Size([10])
    
In: input = t.randn(1, 1, 32, 32)
    out = net(input)
    out.size()
Out:torch.Size([1, 10])

In: net.zero_grad() # 所有参数的梯度清零
    out.backward(t.ones(1, 10)) # 反向传播

注意:torch.nn只支持输入mini-batch,不支持一次只输入一个样本。如果只输入一个样本,那么需要使用 input.unsqueeze(0)将batch_size设为1。例如, nn.Conv2d的输入必须是4维,形如$\text{nSamples} \times \text{nChannels} \times \text{Height} \times \text{Width}$ 。如果一次输入只有一个样本,那么可以将$\text{nSample}$ 设置为1,即$1 \times \text{nChannels} \times \text{Height} \times \text{Width}$ 。

3.2 损失函数

torch.nn实现了神经网络中大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失等,下面举例说明:

In: output = net(input)
    target = t.arange(0, 10).view(1, 10).float() 
    criterion = nn.MSELoss()
    loss = criterion(output, target)
    loss 
Out:tensor(28.1249, grad_fn=<MseLossBackward>)

对loss进行反向传播溯源(使用gradfn属性),可以看到上文实现的LeNet的计算图如下:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d  
      -> view -> linear -> relu -> linear -> relu -> linear 
      -> MSELoss
      -> loss

当调用loss.backward()时,计算图会动态生成并自动微分,自动计算图中参数(parameters)的导数,示例如下:

In: # 运行.backward,观察调用之前和调用之后的grad
    net.zero_grad() # 把net中所有可学习参数的梯度清零
    print('反向传播之前 conv1.bias的梯度')
    print(net.conv1.bias.grad)
    loss.backward()
    print('反向传播之后 conv1.bias的梯度')
    print(net.conv1.bias.grad)
Out:反向传播之前 conv1.bias的梯度
    tensor([0., 0., 0., 0., 0., 0.])
    反向传播之后 conv1.bias的梯度
    tensor([ 0.0020, -0.0619,  0.1077,  0.0197,  0.1027, -0.0060])

3.3 优化器

在完成反向传播中所有参数的梯度计算后,需要使用优化方法来更新网络的权重和参数。常用的随机梯度下降法(SGD)的更新策略如下:

weight = weight - learning_rate * gradient

用户可以手动实现这一更新策略:

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate) # inplace减

torch.optim中实现了深度学习中大多数优化方法,例如RMSProp、Adam、SGD等,因此,通常情况下用户不需要手动实现上述代码。下面举例说明如何使用torch.optim进行网络的参数更新:

In: import torch.optim as optim
    #新建一个优化器,指定要调整的参数和学习率
    optimizer = optim.SGD(net.parameters(), lr = 0.01)
    
    # 在训练过程中
    # 先梯度清零(与net.zero_grad()效果一样)
    optimizer.zero_grad() 
    
    # 计算损失
    output = net(input)
    loss = criterion(output, target)
    
    #反向传播
    loss.backward()
    
    #更新参数
    optimizer.step()

3.4 数据加载与预处理

在深度学习中,数据加载及预处理是非常繁琐的过程。幸运的是,PyTorch提供了一些可以极大简化和加快数据处理流程的工具:DatasetDataLoader。同时,对于常用的数据集,PyTorch提供了封装好的接口供用户快速调用,这些数据集主要保存在torchvision中。torchvision是一个视觉工具包,它提供了许多视觉图像处理的工具,主要包含以下三部分。

  • datasets:提供了常用的数据集,如MNIST、CIFAR-10、ImageNet等。
  • models:提供了深度学习中经典的网络结构与预训练模型,如ResNet、MobileNet等。
  • transforms:提供了常用的数据预处理操作,主要包括对Tensor、PIL Image等的操作。

读者可以使用torchvision方便地加载数据,然后进行数据预处理,这部分内容会在本书第5章进行详细介绍。

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

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

相关文章

docker、k8s部署 mysql group replication 和 ProxySQL 读写分离

MySQL Group Replication&#xff08;简称MGR&#xff09;是MySQL官方推出的一个高可用与高扩展的解决方案。MySQL组复制它提供了高可用、高扩展、高可靠的MySQL集群服务&#xff0c;这里部署的 mysql 版本 5.7.33&#xff0c;架构是一读一写。特别要注意一个关键点: 必须保证各…

sqli-labs-php7-master第11-16关

猜注入点 先来猜数字型 单引号字符型&#xff1a; 发现注入点找到了 猜测数据库有多少个字段&#xff1a; 1’ order by 4 # 密码随便输的。 这里没有使用--注释&#xff0c;因为没作用&#xff0c;可能是过滤掉了 继续猜。刚才没猜对 1 order by 2 # 没报错&#xff0c;猜…

如何将neo4j,4.x版本部署到服务器上

一. 简介 当我们使用neo4j构建知识图谱时&#xff0c;我们希望让别人能和我们共用neo4j进行知识图谱的构建&#xff0c;我们的方法之一就是将neo4j部署到我们的服务器上&#xff0c;然后将7474,7687端口暴露出来&#xff0c;这样就可以通过访问服务器公网IP的7474端口来操作我…

电脑硬盘坏了数据可以恢复吗?如何恢复硬盘数据?

电脑硬盘坏了数据可以恢复吗&#xff1f;对于这种问题&#xff0c;还需要具体问题具体分析的&#xff0c;一般是可以恢复。 硬盘损坏可以分为物理损坏和逻辑损坏两种情况&#xff1a; 1.逻辑损坏 这通常是由于软件问题&#xff0c;如文件系统错误、病毒攻击、误删除、格式化等…

CentOS Linux release 7.9.2009 中sudo命令未找到

先在 Windows 环境中下载 sudo 的安装包 选择适合自己 Centos 版本的安装包下载到本地&#xff1a;https://www.sudo.ws/releases/stable/ 然后把安装包拷贝的 Centos &#xff08;Linux系统&#xff09;中&#xff0c;cd 进入安装包所在的目录执行下面的命令&#xff1a; 格…

【Unity】线性代数基础:矩阵、矩阵乘法、转置矩阵、逆矩阵、正交矩阵等

文章目录 矩阵&#xff08;Matrix&#xff09;矩阵能干啥&#xff1f;矩阵基本运算矩阵加减法矩阵和标量的乘法矩阵和矩阵的乘法矩阵的转置矩阵相等 特殊的矩阵方块矩阵对称矩阵对角元素&#xff08;Diagonal Elements&#xff09;对角矩阵&#xff08;Diagonal Matrix&#xf…

sqli-labs-master初学者题目练习

Less-1 从源码可以看出id为注入点&#xff0c;且为单引号过滤 使用 闭合 --为注释 原本应该用--‘space’&#xff0c;但-与‘连在一起无法起到注释作用 order by为联合查询——同时查询两张表&#xff0c;但两张表列数必须相同 所有从以上两张图可以看出此表格有三列数据 爆…

计算机网络知识汇总(超详细整理)从零基础入门到精通,看完这一篇就够了

文章目录 前言一、计算机网络概述 1 互联网的构成2.网络分类3.接入网4.网络核心的两大功能 ①路由②转发 5.网络分层 ①OSI 7层模型②TCP/IP 4层模型③两种模型比较 二、物理层 1.物理介质 ①引导型介质②非引导型介质 2.数据交换方式 ①分组交换②电路交换 3.信道复用 …

在亚马逊云科技AWS上利用PEFT和RLHF高效微调AI大模型减少有害回复

简介&#xff1a; 小李哥将继续每天介绍一个基于亚马逊云科技AWS云计算平台的全球前沿AI技术解决方案&#xff0c;帮助大家快速了解国际上最热门的云计算平台亚马逊云科技AWS AI最佳实践&#xff0c;并应用到自己的日常工作里。 本次我将介绍如何用亚马逊云科技的AI模型训练服…

基于K8S部署安装Jenkins

基于K8S部署安装Jenkins 1.Jenkins Kubernetes 清单文件2.Kubernetes Jenkins 部署1&#xff1a;为 Jenkins 创建 Namespace。 最好将所有DevOps工具分类为与其他应用程序分开的命名空间。2&#xff1a;创建“serviceAccount.yaml”文件并复制以下管理员服务帐户清单。1. kubec…

174.地下城游戏——LeetCode

题目 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻…

文章生成器免费版,自动写作文章让你无创作之忧

对于创作者而言&#xff0c;长期的内容输出都会遇到写作瓶颈发生&#xff0c;这常常让许多创作者陷入写作困难。而当前解决写作困难最好的方法就是文章生成器了&#xff0c;它自动写作文章的优势让广大的创作者有目共睹&#xff0c;并且是很多创作者们在内容创作中人手必备的神…

Gateway实现Redis拉取信息+用户模块开发

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.Gateway实现Redis拉取信息1.目录结构2.RedisConfig.java3.Re…

无人机动力系统详解

一、动力源 动力源是无人机动力系统的核心&#xff0c;负责提供飞行所需的能量。 二、传动系统 传动系统负责将动力源产生的能量传递到无人机的旋翼或螺旋桨上&#xff0c;使其产生升力。 三、控制系统 控制系统是无人机动力系统的“大脑”&#xff0c;它根据飞行指令和传…

C++ 正则表达式调试器

设计背景 部分网页正则表达式测试工具&#xff0c;输入$匹配符会卡死&#xff0c;决定自己实现一个 界面设计 关键代码 void RegexpDialog::on_edt_regx_textChanged(const QString &pattern) {auto text ui->edt_content->text();QRegularExpression regxp(patte…

C语言 ——— 学习并使用 strtok 函数

目录 strtok函数的功能 strtok函数的参数以及返回值​编辑 使用strtok函数 使用方法一&#xff1a;根据需要分段的字符串写代码 使用方法二&#xff1a;配合for循环巧妙使用 strtok函数的功能 将字符串拆分为各个段&#xff0c;举例说明&#xff1a; 输入&#xff1a; 第…

LVS-DR模式集群:案例与概念

DR模式&#xff08;直接路由&#xff09; 概念 Direct Routing&#xff0c;简称DR模式采用半开放式的网络结构&#xff0c;与TUN模式的结构类似&#xff0c;但内网服务器并不是分散在各地&#xff0c;而是与调度器位于同一个物理网络负载调度器与内网服务器通过本地网络连接&a…

SQL Server 临时存储过程及示例

在本文中&#xff0c;我们将深入探讨 SQL Server 中的临时存储过程&#xff0c;并提供一些实际的示例。在我们之前的文章中&#xff0c;我们讨论了 SQL Server 存储过程中的返回值。本文将详细介绍以下内容&#xff1a; 什么是 SQL Server 临时存储过程&#xff1f; 在数据库…

什么是面包板和杜邦线?

1.面包板&#xff1a;搭建电路 2.杜邦线&#xff1a;电流的连接线 分为三类&#xff1a;公公线&#xff0c;公母线&#xff0c;母母线

UE GAS学习

【Unreal】虚幻GAS系统快速入门-CSDN博客 GameplayTags FGameplayTags是一种层级标签&#xff0c;如Parent.Child.GrandChild。 通过GameplayTagManager进行注册。替代了原来的Bool&#xff0c;或Enum的结构&#xff0c;可以在玩法设计中更高效地标记对象的行为或状态。 Gamep…