四、深度学习的计算

news2024/11/17 3:51:08

文章目录

  • 前言
  • 一、层和块
    • 1.1 自定义块
    • 1.2 顺序块
    • 1.3 在前向传播函数中执行代码
    • 1.4 效率问题
    • 1.5 小结
  • 二、参数管理
    • 2.1 参数访问
      • 2.1.1 目标参数
      • 2.1.2 访问所有参数
      • 2.1.3 从嵌套块中收集参数
    • 2.2 参数初始化
      • 2.2.1 内置初始化
      • 2.2.2 自定义初始化
      • 2.2.3 参数绑定
  • 三、延后初始化
  • 四、自定义层
    • 4.1 不带参数的层
    • 4.2 带参数的层
  • 五、读写文件
    • 5.1 加载和保存张量
    • 5.2 保存和加载模型参数
  • 六、GPU
    • 6.1 计算设备
    • 6.2 张量与GPU
      • 6.2.1 存储在GPU上
      • 6.2.2 复制
    • 6.3 神经网络与GPU


前言

之前我们已经介绍了一些基本的机器学习概念, 并慢慢介绍了功能齐全的深度学习模型。 在上一章中,我们从零开始实现了多层感知机的每个组件, 然后展示了如何利用高级API轻松地实现相同的模型。 为了易于学习,我们调用了深度学习库,但是跳过了它们工作的细节。 在本章中,我们将深入探索深度学习计算的关键组件, 即模型构建、参数访问与初始化、设计自定义层和块、将模型读写到磁盘, 以及利用GPU实现显著的加速。 这些知识将使读者从深度学习“基础用户”变为“高级用户”。 虽然本章不介绍任何新的模型或数据集, 但后面的高级模型章节在很大程度上依赖于本章的知识。

一、层和块

事实证明,研究讨论“比单个层大”但“比整个模型小”的组件更有价值。

为了试下这些复杂的网络,我们引入了神经网络块的概念。块(block)可以描述单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的一个好处是可以将一些块组合成更大的组件, 这一过程通常是递归的,如图所示。 通过定义代码来按需生成任意复杂度的块, 我们可以通过简洁的代码实现复杂的神经网络。

在这里插入图片描述
从编程的角度来看,块由类表示。它的任何子类都必须定义一个将其输入转换为输出的前向传播函数, 并且必须存储任何必需的参数。

1.1 自定义块

要想直观地了解块是如何工作的,最简单的方法就是自己实现一个。 在实现我们自定义块之前,我们简要总结一下每个块必须提供的基本功能。

  1. 将输入数据作为其前向传播函数的参数。

  2. 通过前向传播函数来生成输出。请注意,输出的形状可能与输入的形状不同。

  3. 计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的。

  4. 存储和访问前向传播计算所需的参数。

  5. 根据需要初始化模型参数。

下面我们自定义一个块:

import torch
from torch import nn
from torch.nn import functional as F

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
        super().__init__()
        self.hidden = nn.Linear(20, 256)  # 隐藏层
        self.out = nn.Linear(256, 10)  # 输出层

    # 定义模型的前向传播,即如何根据输入X返回所需的模型输出
    def forward(self, X):
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        return self.out(F.relu(self.hidden(X)))
#调用:
net = MLP()
net(X)

块的一个主要优点就是它的多功能性。如:我们可以子类化块以创建层、整个模块或具有中等复杂度的各种组件。

1.2 顺序块

Sequential类的使用:

net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

X = torch.rand(2, 20)
net(X)

现在我们来自习琢磨一下pytorch中的Sequential类是如何工作的,回想下Sequential类的功能,我们需要这两个功能:

  1. 一种将块逐个追加到列表中的函数;
  2. 种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。

自定义实现类似Sequential的功能:

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            # 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
            # 变量_modules中。_module的类型是OrderedDict
            self._modules[str(idx)] = module

    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X
        
net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

Module类中有一个_modules的字典,它的主要优点是:在模块的参数初始化过程中,系统知道在_modules字典中查找需要初始化参数的子块。

1.3 在前向传播函数中执行代码

Sequential类虽然简单,但是并不是所有的架构都是简单的顺序架构,有时候我们需要在前向传播中加入python的控制流。如:

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        X = self.linear(X)
        # 使用创建的常量参数以及relu和mm函数
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        # 复用全连接层。这相当于两个全连接层共享参数
        X = self.linear(X)
        # 控制流
        while X.abs().sum() > 1:
            X /= 2
        return X.sum()

在这个FixedHiddenMLP模型中,我们实现了一个隐藏层, 其权重(self.rand_weight)在实例化时被随机初始化,之后为常量。 这个权重不是一个模型参数,因此它永远不会被反向传播更新。 然后,神经网络将这个固定层的输出通过一个全连接层。

注意,在返回输出之前,模型做了一些不寻常的事情: 它运行了一个while循环,在L1范数大于1的条件下, 将输出向量除以2,直到它满足条件为止。 最后,模型返回了X中所有项的和。 注意,此操作可能不会常用于在任何实际任务中, 我们只展示如何将任意代码集成到神经网络计算的流程中。

我们也可以嵌套块:

class NestMLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(20, 64), nn.ReLU(),
                                 nn.Linear(64, 32), nn.ReLU())
        self.linear = nn.Linear(32, 16)

    def forward(self, X):
        return self.linear(self.net(X))

chimera = nn.Sequential(NestMLP(), nn.Linear(16, 20), FixedHiddenMLP())
chimera(X)

1.4 效率问题

注:我们在模型中加入python的代码可能会拖慢执行速度,因为GPU上的运行极快,提高Python速度的最好方法是完全避免使用Python。

1.5 小结

  • 一个块可以由许多层组成;一个块可以由许多块组成。

  • 块可以包含代码。

  • 块负责大量的内部处理,包括参数初始化和反向传播。

  • 层和块的顺序连接由Sequential块处理。

二、参数管理

在本部分,我们将介绍一下内容:

  • 访问参数,用于调试、诊断和可视化;
  • 参数初始化;
  • 在不同模型组件间共享参数。

先看下单隐藏层的多层感知机:

import torch
from torch import nn

net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)

2.1 参数访问

我们可以通过索引来访问模型的任意层,这就像模型是一个列表,列表中每个元素的state_dict属性存放参数。如:

print(net[0])
print(net[0].state_dict())
print(net[1])
print(net[2])
Linear(in_features=4, out_features=8, bias=True)
OrderedDict([('weight', tensor([[ 0.1200,  0.0939,  0.3031,  0.4134],
        [ 0.3665,  0.2180,  0.0131,  0.4878],
        [ 0.3440, -0.4701,  0.0074,  0.2202],
        [-0.2532, -0.4209,  0.0032,  0.4046],
        [ 0.4745,  0.0568,  0.1719,  0.2846],
        [-0.1002, -0.3926,  0.0540,  0.2673],
        [ 0.4484,  0.3800, -0.2103, -0.4893],
        [-0.1503,  0.4347,  0.1127,  0.3866]])), ('bias', tensor([ 0.1516, -0.3084,  0.0946,  0.0946, -0.4010,  0.3626, -0.4877,  0.3704]))])
ReLU()
Linear(in_features=8, out_features=1, bias=True)

2.1.1 目标参数

注意,每个参数都表示为参数类的一个实例。 要对参数执行任何操作,首先我们需要访问底层的数值。 有几种方法可以做到这一点。有些比较简单,而另一些则比较通用。 下面的代码从第二个全连接层(即第三个神经网络层)提取偏置, 提取后返回的是一个参数类实例,并进一步访问该参数的值。

print(type(net[2].bias))
print(net[2].bias)
print(net[2].bias.data)
<class 'torch.nn.parameter.Parameter'>
Parameter containing:
tensor([-0.0291], requires_grad=True)
tensor([-0.0291])

参数是一个符合的对象,它具有值(.data)、梯度(.grad)和额外的信息属性.

net[2].weight.grad == None

2.1.2 访问所有参数

当我们需要对所有参数执行操作时,逐个访问它们可能会很麻烦。下面,我们将通过演示来比较访问第一个全连接层的参数和访问所有层。

print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
('weight', torch.Size([8, 4])) ('bias', torch.Size([8]))
('0.weight', torch.Size([8, 4])) ('0.bias', torch.Size([8])) ('2.weight', torch.Size([1, 8])) ('2.bias', torch.Size([1]))

2.1.3 从嵌套块中收集参数

首先,我们定义一个生成块的函数:

def block1():
    return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                         nn.Linear(8, 4), nn.ReLU())

def block2():
    net = nn.Sequential()
    for i in range(4):
        # 在这里嵌套
        net.add_module(f'block {i}', block1())
    return net

rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)

我们可以直接打印网络模型,看其结构:

print(rgnet)
Sequential(
  (0): Sequential(
    (block 0): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 1): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 2): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
    (block 3): Sequential(
      (0): Linear(in_features=4, out_features=8, bias=True)
      (1): ReLU()
      (2): Linear(in_features=8, out_features=4, bias=True)
      (3): ReLU()
    )
  )
  (1): Linear(in_features=4, out_features=1, bias=True)
)

通过索引打印参数:

rgnet[0][1][0].bias.data

2.2 参数初始化

知道了如何访问参数后, 我们需要看下如何正确的初始化参数。

默认情况下,PyTorch会根据一个范围均匀地初始化权重和偏置矩阵, 这个范围是根据输入和输出维度计算出的。 PyTorch的nn.init模块提供了多种预置初始化方法。

2.2.1 内置初始化

让我们首先调用内置的初始化器。 下面的代码将所有权重参数初始化为标准差为0.01的高斯随机变量, 且将偏置参数设置为0。

def init_normal(m):
    if type(m) == nn.Linear:
        nn.init.normal_(m.weight, mean=0, std=0.01)
        nn.init.zeros_(m.bias)
net.apply(init_normal)
net[0].weight.data[0], net[0].bias.data[0]

net.apply是PyTorch中的一个方法,用于将一个函数应用到一个网络模型的所有参数上。

我们还可以将所有参数初始化为给定的常数,比如初始化为1。

def init_constant(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 1)
        nn.init.zeros_(m.bias)
net.apply(init_constant)
net[0].weight.data[0], net[0].bias.data[0]

我们还可以对某些块应用不同的初始化方法。 例如,下面我们使用Xavier初始化方法初始化第一个神经网络层, 然后将第三个神经网络层初始化为常量值42。

def init_xavier(m):
    if type(m) == nn.Linear:
        nn.init.xavier_uniform_(m.weight)
def init_42(m):
    if type(m) == nn.Linear:
        nn.init.constant_(m.weight, 42)

net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)

2.2.2 自定义初始化

有时,深度学习框架没有提供我们需要的初始化方法。 在下面的例子中,我们使用以下的分布为任意权重参数w定义初始化方法:
w ∼ { U ( 5 , 10 )  可能性  1 4 0  可能性  1 2 U ( − 10 , − 5 )  可能性  1 4 \begin{split}\begin{aligned} w \sim \begin{cases} U(5, 10) & \text{ 可能性 } \frac{1}{4} \\ 0 & \text{ 可能性 } \frac{1}{2} \\ U(-10, -5) & \text{ 可能性 } \frac{1}{4} \end{cases} \end{aligned}\end{split} w U(5,10)0U(10,5) 可能性 41 可能性 21 可能性 41

def my_init(m):
    if type(m) == nn.Linear:
        print("Init", *[(name, param.shape)
                        for name, param in m.named_parameters()][0])
        nn.init.uniform_(m.weight, -10, 10)
        m.weight.data *= m.weight.data.abs() >= 5

net.apply(my_init)
net[0].weight[:2]

我们也可以直接设置参数:

net[0].weight.data[:] += 1
net[0].weight.data[0, 0] = 42
net[0].weight.data[0]

2.2.3 参数绑定

有时我们希望在多个层间共享参数: 我们可以定义一个稠密层,然后使用它的参数来设置另一个层的参数。

# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
                    shared, nn.ReLU(),
                    shared, nn.ReLU(),
                    nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100  #修改一下一个层的参数
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])

这个例子表明第三个和第五个神经网络层的参数是绑定的。 它们不仅值相等,而且由相同的张量表示。 因此,如果我们改变其中一个参数,另一个参数也会改变。 这里有一个问题:当参数绑定时,梯度会发生什么情况? 答案是由于模型参数包含梯度,因此在反向传播期间第二个隐藏层 (即第三个神经网络层)和第三个隐藏层(即第五个神经网络层)的梯度会加在一起。

三、延后初始化

延后初始化能让我们先定义整个模型,而不用关心它输入的维度。

import torch
from torch import nn
from d2l import torch as d2l
net = nn.Sequential(nn.LazyLinear(256), nn.ReLU(), nn.LazyLinear(10))

此时,我们看看权重:

net[0].weight

结果是:

<UninitializedParameter>

当我们给定输入时,模型会自定初始化参数:

X = torch.rand(2, 20)
net(X)

net[0].weight.shape
torch.Size([256, 20])

四、自定义层

深度学习成功背后的一个因素就是神经网络的灵活性,有时我们为了解决特定的问题需要自定义一个框架中不存在的层。目前我们已知的有Linear层,接下来我们展示如何自定义层。

4.1 不带参数的层

首先,我们构造一个没有任何参数的自定义层。下面的CenteredLayer类要从其输入中减去均值。 要构建它,我们只需继承基础层类并实现前向传播功能。

import torch
import torch.nn.functional as F
from torch import nn


class CenteredLayer(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, X):
        return X - X.mean()

使用:

net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())
Y = net(torch.rand(4, 8))
Y.mean()

结果为1个很小很小的数。

4.2 带参数的层

class MyLinear(nn.Module):
    def __init__(self, in_units, units):
        super().__init__()
        self.weight = nn.Parameter(torch.randn(in_units, units))
        self.bias = nn.Parameter(torch.randn(units,))
    def forward(self, X):
        linear = torch.matmul(X, self.weight.data) + self.bias.data
        return F.relu(linear)

接下来我们实例化并访问参数:

linear = MyLinear(5, 3)
linear.weight
Parameter containing:
tensor([[ 1.9094, -0.8244, -1.6846],
        [ 0.6850,  0.8366, -1.3837],
        [ 0.0289,  2.0976,  1.3855],
        [-0.8574, -0.3557, -0.4109],
        [ 2.2963, -1.3008,  1.2173]], requires_grad=True)

使用:

net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(torch.rand(2, 64))

五、读写文件

到目前为止,我们讨论了如何处理数据, 以及如何构建、训练和测试深度学习模型。 然而,有时我们希望保存训练的模型, 以备将来在各种环境中使用(比如在部署中进行预测)。 此外,当运行一个耗时较长的训练过程时, 最佳的做法是定期保存中间结果, 以确保在服务器电源被不小心断掉时,我们不会损失几天的计算结果。 因此,现在是时候学习如何加载和存储权重向量和整个模型了。

5.1 加载和保存张量

对于单个张量,我们可以直接调用loadsave函数分别读写它们。 这两个函数都要求我们提供一个名称,save要求将要保存的变量作为输入。

import torch
from torch import nn
from torch.nn import functional as F

x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')

我们也可以保存一个张量列表或张量字典,然后再读取回来:

y = torch.zeros(4)
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')

5.2 保存和加载模型参数

class MLP(nn.Module):
    def __init__(self):
        super().__init__()
        self.hidden = nn.Linear(20, 256)
        self.output = nn.Linear(256, 10)

    def forward(self, x):
        return self.output(F.relu(self.hidden(x)))

net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)

接下来,我们将模型的参数存储在一个叫做“mlp.params”的文件中。

torch.save(net.state_dict(), 'mlp.params')

加载:

clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval() #将模型设置为预测模式 不反向传播更新参数

六、GPU

在命令行我们可以输入nvidia-smi查看GPU信息:
在这里插入图片描述

6.1 计算设备

在pytorch中,可以使用如下方法调用:

import torch
from torch import nn

torch.device('cpu'), torch.device('cuda'), torch.device('cuda:1')

查询可用gpu数:

torch.cuda.device_count()

6.2 张量与GPU

x = torch.tensor([1, 2, 3])
x.device  #可以查看当前x张量在cpu还是gpu上

需要注意的是,无论何时我们要对多个项进行操作, 它们都必须在同一个设备上。 例如,如果我们对两个张量求和, 我们需要确保两个张量都位于同一个设备上, 否则框架将不知道在哪里存储结果,甚至不知道在哪里执行计算。

6.2.1 存储在GPU上

法一:

X = torch.ones(2, 3, device=torch.device('cuda:0'))  #放到gpu0上

法二:

X = torch.ones(2, 3)
X.to(torch.device('cuda:0'))

法三:

X = torch.ones(2, 3)
X.cuda()

6.2.2 复制

如果我们要计算X + Y,我们需要决定在哪里执行这个操作。 例如,如图所示, 我们可以将X传输到第二个GPU并在那里执行操作。 不要简单地X加上Y,因为这会导致异常, 运行时引擎不知道该怎么做:它在同一设备上找不到数据会导致失败。 由于Y位于第二个GPU上,所以我们需要将X移到那里, 然后才能执行相加运算。
在这里插入图片描述

Z = X.cuda(1)
print(X)
print(Z)

结果:

tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:0')
tensor([[1., 1., 1.],
        [1., 1., 1.]], device='cuda:1')

此时如果X+Z,则:
报错:RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:1 and cpu!

6.3 神经网络与GPU

我们也可以把模型放到GPU上:

net = nn.Sequential(nn.Linear(3, 1))
net = net.to(device=torch.device('cuda:0'))

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

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

相关文章

低功耗蓝牙OM6621EM 兼容Nordic 51系列2.4G私有协议

OM6621EM是一个功率优化的系统(SOC).解决蓝牙低功耗和专有的2.4 ghz应用。它集成了一个高具有蓝牙基带和丰富外设的低功耗射频收发器I0扩展。OM6621EM还集成了电源管理单元(PMU)来提供高效的电源管理。它的目标是2.4GHz低功耗蓝牙系统&#xff0c;专有的2.4 ghz系统&#xff0c…

easyX库其他函数(注释版)

本篇是easyX库系列正文最后一篇&#xff0c;依旧是有几个很有价值的函数&#xff0c;我不补充了几个例子&#xff0c;对easyX库中的部分code例子做了修改。 0.其它函数概览 函数或数据类型描述GetEasyXVer获取当前EasyX库的版本信息。BeginBatchDraw开始批量绘图。EndBatchDr…

Java Web HTMLCSS(1)23.6.29

HTML&CSS 1&#xff0c;HTML 1.1 介绍 HTML 是一门语言&#xff0c;所有的网页都是用HTML 这门语言编写出来的&#xff0c;也就是HTML是用来写网页的&#xff0c;像京东&#xff0c;12306等网站有很多网页。 这些都是网页展示出来的效果。而HTML也有专业的解释 HTML(Hy…

Pycharm中打开HTML文件报错:Windows 找不到文件‘chrome‘

问题现象&#xff1a; Pycharm中&#xff0c;打开HTML文件&#xff0c;选择chrome浏览器打开时&#xff0c;报错&#xff1a;Windows 找不到文件’chrome’。请确定文件名是否正确后&#xff0c;再试一次。但实际上你的电脑上是安装了Chrome浏览器的。 解决方案&#xff1a; 原…

【算法】状态机DP 买卖股票系列

文章目录 前期知识股票问题买卖股票的最佳时机 II最佳买卖股票时机含冷冻期买卖股票的最佳时机 IV补充&#xff1a;恰好k次 / 至少k次 怎么做&#xff1f; 相关题目练习买卖股票的最佳时机 https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/解法1——状态机DP解法…

十三、禅道登录/提交版本/编辑版本接口

十三、禅道提交版本/编辑版本接口 1. 禅道的登录接口或者叫获取tokens接口 # -*- coding: utf-8 -*- """ ------------------------------------------------------------------------------- File : zentao_login.py Time : 2023/6/29 13:55 author : …

Java读取Excel第一行数据,获取表头

目录 一、场景 二、代码实现 1、工具类 2、方法调用 3、结果 之前写过一篇关于解析Excel的博客&#xff1a;解析读取Excel文件&#xff08;.xls .xlsx&#xff09;&#xff0c;今天再分享一下&#xff0c;如何获取Excel的表头数据。 一、场景 需要判断导入的Excel文件的列…

什么是AQS

AQS&#xff08;Abstract Queued Synchronizer&#xff09;是一个抽象的队列同步器&#xff0c;通过维护一个共享资源状态&#xff08;Volatile Int State&#xff09;和一个先进先出&#xff08;FIFO&#xff09;的线程等待队列来实现一个多线程访问共享资源的同步框架。 AQS…

今日份分享:三个电脑mp3转换器推荐

有一个音乐爱好者叫小艾。她对音乐充满热爱&#xff0c;每天都会用耳机沉浸在动听的旋律中。然而&#xff0c;她最近遇到了一个问题&#xff1a;她手头有一些喜欢的音乐文件&#xff0c;但格式却是不支持她的音乐播放器。这让她感到非常困扰&#xff0c;因为她希望随时随地欣赏…

适用于Vue 3的最佳开源分页库

从头开始实现分页可能是一项耗时的任务&#xff0c;需要大量的精力和资源。幸运的是&#xff0c;有几个伟大的开源库可以简化这个过程&#xff0c;提高你的效率。使用分页库可以节省你的时间和精力&#xff0c;使你能够专注于建立你的应用程序的其他更重要的功能。 在这篇文章…

如何选择适合自己的专业?

高考季又到了&#xff0c;毕业生们正忙着选填志愿。志愿填报是一个关键的决策&#xff0c;它将对他们未来的学习和就业产生重要影响。在这个关键的时刻&#xff0c;一些相关问题随之而来&#xff1a;如何填报志愿&#xff1f;是选择专业还是学校&#xff1f;哪些专业就业前景好…

全国农信银CTF逆向Baby8or解析

按CTRL X 跳转到main函数&#xff0c;按F5 生成伪代码进行分析。 发现一个加密函数&#xff0c;当加密后的数据和genc[i]数组中的值对比一致则表示正确flag&#xff0c;字符串长度为35。 双击genc[i] 找到genc[i]中的数据&#xff0c;转成10进制得到数组 genc[172,102,148,22…

数据结构--双链表

数据结构–双链表 单链表 VS 双链表 单链表&#xff1a;无法逆向检索&#xff0c;有时候不太方便 双链表&#xff1a;可进可退&#xff0c;存储密度更低一丢丢 双链表的定义 typedef struct DNode {ElemType data;struct DNode *prior, *next; }DNode, *DLinkList;双链表的初…

JavaWeb——2.注解

这篇文章我们来讲一下Java中的注解 其实这部分内容算是Javaweb的补充内容&#xff0c;其中还包括Junit测试和反射的相关内容。 Junit测试是一个比较简单的内容&#xff0c;这里就不写了&#xff1b;而反射的相关内容可以看java基础专栏&#xff0c;那里面有详细的叙述。 目录…

Jvm jmx_exporter Prometheus dubbo Grafana 重点看端口要对应上 单独进程和程序进程内jmx_exporter

目录 JMX Exporter 的两种用法 启动独立进程 jmx_prometheus_httpserver-0.18.0.jar 方式 下载 jmx_exporter 找地方随便一放 创建配置文件 config_jmx_exporter.yaml 增加 启动 jvm 配置 一定要是jvm参数 可别意外写成程序参数 启动jmx_exporter Prometheus yml 配置 …

使用jmap查看对象数

jmap&#xff1a;JVM自带的一种内存映像工具 查看jmap命令帮助 查询java进程pid # 查看堆内存中的对象 jmap -histo PID# 查看堆内存中的存活对象 jmap -histo:live PID 使用示例&#xff1a; jmap -histo:live 46024|grep com.kingbase8.jdbc.KbConnection 列说明 num#insta…

1.4、Java的标识符 关键字 注释 变量 数据类型与类型转换

1 JAVA语法基础 1.1 标识符 标识符可以简单的理解成一个名字。 在Java中&#xff0c;我们需要给代码中的很多元素起名&#xff0c;包括类名、方法名、字段名、变量名等等。我们给对应元素起的名称就被称为标识符&#xff0c;一个正确的标识符需要遵循以下规则&#xff1a; 1、…

简单实现接口自动化测试(基于python+unittest)

目录 简单实现接口自动化测试(基于pythonunittest) 简介 引言 为什么要做接口自动化测试? 为什么要自己写框架呢? 一个现有的简单接口例子 接口信息如下 测试思路 原始脚本实现 未优化 优化 第一版 优化 第二版 优化 第三版 最终输出日志信息 后续改进建议 总…

ubuntu20.04系统4060安装cuda11.8和cudnn8.6

ubuntu20.04系统4060安装cuda11.8和cudnn8.6 一&#xff1a;安装nvidia-driver-525 1&#xff1a;查看本机显卡能够配置的驱动信息 在终端输入&#xff1a; ubuntu-drivers devices2&#xff1a;推荐安装的版本号是&#xff1a; 安装代码&#xff1a; sudo apt install n…

JPA-querydsl增强工具,query-dsl-plus,现在已开源并推送到mvnrepository

前言 由于喜欢使用JPA&#xff0c;所以后续就接触了query-dsl&#xff0c;但是呢&#xff0c;随着需求的变更&#xff0c;数据查询条件也会越来越复杂&#xff08;这个和jpa没关系&#xff0c;就算使用mybatis也一样&#xff09;&#xff0c;往往前端改动了&#xff0c;后端还…