目录

1. 层和块

1.1 自定义块

1.2 顺序块

1.3 一些灵活写法的例子(主要是展示写法自由度)

2. 参数管理

2.1 参数访问

2.1.1 目标函数

2.1.2 一次性访问所有参数

2.1.3 从嵌套块收集参数

2.2 参数初始化

2.2.1. 内置初始化

2.2.2 自定义初始化

2.2.3 参数绑定

3. 自定义层

3.1 不带参数的层

3.2 带参数的层

4. 读写文件

4.1 加载和保存张量

4.2 加载和保存模型参数

5. QA环节

5.1 如果类别转化采用独热编码,把类别变量转化为伪变量的时候内存炸了怎么办?

5.2 object数据该怎么处理,离散类别太多了,维度炸了?

5.3 我们创建好网络之后,torch是按声明规则给参数初始化的?


1. 层和块

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

在构造自定义块之前,我们先回顾一下第三章多层感知机的代码。 下面的代码生成一个网络,其中包含一个具有256个单元和ReLU激活函数的全连接隐藏层, 然后是一个具有10个隐藏单元且不带激活函数的全连接输出层。

import torch
from torch import nn
from torch.nn import functional as Fnet = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))X = torch.rand(2, 20)
net(X)

在这个例子中,我们通过实例化nn.Sequential来构建我们的模型, 层的执行顺序是作为参数传递的。 简而言之,nn.Sequential定义了一种特殊的Module, 即在PyTorch中表示一个块的类, 它维护了一个由Module组成的有序列表。 注意,两个全连接层都是Linear类的实例, Linear类本身就是Module的子类。 另外,到目前为止,我们一直在通过net(X)调用我们的模型来获得模型的输出。 这实际上是net.__call__(X)的简写。 这个前向传播函数非常简单: 它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。

1.1 自定义块

在下面的代码片段中,我们从零开始编写一个块。 它包含一个多层感知机,其具有256个隐藏单元的隐藏层和一个10维输出层。 注意,下面的MLP类继承了表示块的类。 我们的实现只需要提供我们自己的构造函数(Python中的__init__函数)和前向传播函数。这里出现的F.relu函数可以通过输入的tensor进行相应激活函数的计算,而此前的nn.ReLU()是生成一个对应传入参数大小的ReLU层

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)))

块的一个主要优点是它的多功能性。 我们可以子类化块以创建层(如全连接层的类)、 整个模型(如上面的MLP类)或具有中等复杂度的各种组件。 我们在接下来的章节中充分利用了这种多功能性, 比如在处理卷积神经网络时。

1.2 顺序块

下面的MySequential类提供了与默认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)] = moduledef forward(self, X):''' OrderedDict保证了按照成员添加的顺序遍历它们'''for block in self._modules.values():X = block(X)return X

_modules的主要优点是: 在模块的参数初始化过程中, 系统知道在_modules字典中查找需要初始化参数的子块。当MySequential的前向传播函数被调用时, 每个添加的块都按照它们被添加的顺序执行。 现在可以使用我们的MySequential类重新实现多层感知机。

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

1.3 一些灵活写法的例子(主要是展示写法自由度)

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 /= 2return X.sum()net = FixedHiddenMLP()
net(X)        ''' tensor(-0.0949, grad_fn=<SumBackward0>) '''

我们可以混合搭配各种组合块的方法。 在下面的例子中,我们以一些想到的方法嵌套块。

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)

2. 参数管理

有时我们希望提取参数,以便在其他环境中复用它们, 将模型保存下来,以便它可以在其他软件中执行, 或者为了获得科学的理解而进行检查。这里我们以一个单层感知机为例子。

import torch
from torch import nnnet = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
'''tensor([[0.2044],[0.2064]], grad_fn=<AddmmBackward0>)'''

2.1 参数访问

我们从已有模型中访问参数。 当通过Sequential类定义模型时, 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样,每层的参数都在其属性中。 如下所示,我们可以检查第二个全连接层的参数。

print(net[2].state_dict()) '''
OrderedDict([('weight', tensor([[ 0.0251, -0.2952, -0.1204, 0.3436, -0.3450, -0.0372,  0.0462,  0.2307]])),
('bias', tensor([0.2871]))])'''

输出的结果告诉我们一些重要的事情: 首先,这个全连接层包含两个参数,分别是该层的权重和偏置。 两者都存储为单精度浮点数(float32)。 注意,参数名称允许唯一标识每个参数,即使在包含数百个层的网络中也是如此。

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.2871], requires_grad=True)
tensor([0.2871])                        '''

参数是复合的对象,包含值、梯度和额外信息。 这就是我们需要显式参数值的原因。 除了值之外,我们还可以访问每个参数的梯度。 在上面这个网络中,由于我们还没有调用反向传播,所以参数的梯度处于初始状态。 

net[2].weight.grad == None    '''
True                          '''

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])) '''
net.state_dict()['2.bias'].data
'''也可以直接用对应参数的名字访问到
tensor([0.2871])            '''

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 netrgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)    '''tensor([[0.1713],[0.1713]], grad_fn=<AddmmBackward0>)    '''

设计了网络后,我们看看它是如何工作的。 

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)
)    '''

因为层是分层嵌套的,所以我们也可以像通过嵌套列表索引一样访问它们。 下面,我们访问第一个主要的块中、第二个子块的第一层的偏置项。

2.2 参数初始化

我们在动手学习深度学习(总结梳理)——7.数值稳定性和模型初始化中讨论了良好初始化的必要性。深度学习框架提供默认随机初始化, 也允许我们创建自定义初始化方法, 满足我们通过其他规则实现初始化权重。

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]    '''
(tensor([-0.0145,  0.0053,  0.0055, -0.0044]), tensor(0.))'''

我们还可以将所有参数初始化为给定的常数,比如初始化为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]    '''
(tensor([1., 1., 1., 1.]), tensor(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)    '''
tensor([-0.4792,  0.4968,  0.6094,  0.3063])
tensor([[42., 42., 42., 42., 42., 42., 42., 42.]])    '''

2.2.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])

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

3. 自定义层

深度学习成功背后的一个因素是神经网络的灵活性: 我们可以用创造性的方式组合不同的层,从而设计出适用于各种任务的架构。 例如,研究人员发明了专门用于处理图像、文本、序列数据和执行动态规划的层。 未来,你会遇到或要自己发明一个现在在深度学习框架中还不存在的层。 在这些情况下,你必须构建自定义层。

3.1 不带参数的层

下面的CenteredLayer类要从其输入中减去均值。 要构建它,我们只需继承基础层类并实现前向传播功能。

import torch
import torch.nn.functional as F
from torch import nnclass CenteredLayer(nn.Module):def __init__(self):super().__init__()def forward(self, X):return X - X.mean()

让我们向该层提供一些数据,验证它是否能按预期工作。

layer = CenteredLayer()
layer(torch.FloatTensor([1, 2, 3, 4, 5]))    '''
tensor([-2., -1.,  0.,  1.,  2.])'''

现在,我们可以将层作为组件合并到更复杂的模型中。

net = nn.Sequential(nn.Linear(8, 128), CenteredLayer())

作为额外的健全性检查,我们可以在向该网络发送随机数据后,检查均值是否为0。 由于我们处理的是浮点数,因为存储精度的原因,我们仍然可能会看到一个非常小的非零数。

Y = net(torch.rand(4, 8))
Y.mean()    '''
tensor(0., grad_fn=<MeanBackward0>)'''

3.2 带参数的层

以上我们知道了如何定义简单的层,下面我们继续定义具有参数的层, 这些参数可以通过训练进行调整。 我们可以使用内置函数来创建参数,这些函数提供一些基本的管理功能。 比如管理访问、初始化、共享、保存和加载模型参数。 这样做的好处之一是:我们不需要为每个自定义层编写自定义的序列化程序。

现在,让我们实现自定义版本的全连接层。 回想一下,该层需要两个参数,一个用于表示权重,另一个用于表示偏置项。 在此实现中,我们使用修正线性单元作为激活函数。 该层需要输入参数:in_unitsunits,分别表示输入数和输出数。

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.datareturn F.relu(linear)

接下来,我们实例化MyLinear类并访问其模型参数。

linear = MyLinear(5, 3)
linear.weight    '''
Parameter containing:
tensor([[-1.4779, -0.6027, -0.2225],[ 1.1270, -0.6127, -0.2008],[-2.1864, -1.0548,  0.2558],[ 0.0225,  0.0553,  0.4876],[ 0.3558,  1.1427,  1.0245]], requires_grad=True)    '''

我们可以使用自定义层直接执行前向传播计算。

linear(torch.rand(2, 5))    '''
tensor([[0.0000, 0.0000, 0.2187],[0.0000, 0.0000, 0.0000]])    '''

我们还可以使用自定义层构建模型,就像使用内置的全连接层一样使用自定义层。

net = nn.Sequential(MyLinear(64, 8), MyLinear(8, 1))
net(torch.rand(2, 64))    '''
tensor([[ 7.4571],[12.7505]])    '''

4. 读写文件

4.1 加载和保存张量

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

import torch
from torch import nn
from torch import functional as Fx = torch.arange(4)
torch.save(x, 'x-file')''' 我们可以通过load直接把文件中的数据读入内存 '''
x2 = torch.load('x-file')
x2    '''
tensor([0, 1, 2, 3])    '''

我们可以存储一个张量列表,然后把它们读回内存,读入的格式也是保存时的列表格式。

y = torch.zeros(4)
torch.save([x, y],'x-files')
x2, y2 = torch.load('x-files')
(x2, y2)    '''
(tensor([0, 1, 2, 3]), tensor([0., 0., 0., 0.]))    '''

我们甚至可以写入或读取从字符串映射到张量的字典。 当我们要读取或写入模型中的所有权重时,这很方便。

mydict = {'x': x, 'y': y}
torch.save(mydict, 'mydict')
mydict2 = torch.load('mydict')
mydict2        '''
{'x': tensor([0, 1, 2, 3]), 'y': tensor([0., 0., 0., 0.])}    '''

4.2 加载和保存模型参数

保存单个权重向量(或其他张量)确实有用, 但是如果我们想保存整个模型,并在以后加载它们, 单独保存每个向量则会变得很麻烦。 毕竟,我们可能有数百个参数散布在各处。 因此,深度学习框架提供了内置函数来保存和加载整个网络。 需要注意的一个重要细节是,这将保存模型的参数而不是保存整个模型。 例如,如果我们有一个3层多层感知机,我们需要单独指定架构。 因为模型本身可以包含任意代码,所以模型本身难以序列化。 因此,为了恢复模型,我们需要用代码生成架构, 然后从磁盘加载参数。 让我们从熟悉的多层感知机开始尝试一下。

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”的文件中。这里我们可以通过这个帖子了解关于参数列表的三个函数net.parameters() & net.named_parameters() & net.state_dict()

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

为了恢复模型,我们实例化了原始多层感知机模型的一个备份。 这里我们不需要随机初始化模型参数,而是直接读取文件中存储的参数。

clone = MLP()
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()    ''' output:
MLP((hidden): Linear(in_features=20, out_features=256, bias=True)(output): Linear(in_features=256, out_features=10, bias=True)
)        '''

由于两个实例具有相同的模型参数,在输入相同的X时, 两个实例的计算结果应该相同。 让我们来验证一下。

Y_clone = clone(X)
Y_clone == Y    ''' output:
tensor([[True, True, True, True, True, True, True, True, True, True],[True, True, True, True, True, True, True, True, True, True]])    '''

5. QA环节

5.1 如果类别转化采用独热编码,把类别变量转化为伪变量的时候内存炸了怎么办?

在特征特别多的情况下,确实容易出现这类问题,有两种解决办法,首先可以使用稀疏矩阵去存。其次真的特别多的情况下,像在竞赛里面可能会用一些别的办法,例如利用他们的地址去存,或者是不要取句子,拿出他们的词去处理,之后NLP也会讲很多这类特征处理方法。

5.2 object数据该怎么处理,离散类别太多了,维度炸了?

实在不好处理的情况下,直接将它拿掉后去训练,未必精度会差特别多,后续还会讲很多别处理办法

5.3 我们创建好网络之后,torch是按声明规则给参数初始化的?

默认kaiming初始化,其实数据初始化才用什么样是比较接近的,它只是想办法将参数控制在一个合理,不会出现梯度变化奇怪的地方

动手学习深度学习(总结梳理)——9. Pytorch神经网络基础相关推荐

  1. 伯禹公益AI《动手学深度学习PyTorch版》Task 05 学习笔记

    伯禹公益AI<动手学深度学习PyTorch版>Task 05 学习笔记 Task 05:卷积神经网络基础:LeNet:卷积神经网络进阶 微信昵称:WarmIce 昨天打了一天的<大革 ...

  2. 《动手学深度学习》PyTorch版GitHub资源

    之前,偶然间看到过这个PyTorch版<动手学深度学习>,当时留意了一下,后来,着手学习pytorch,发现找不到这个资源了.今天又看到了,赶紧保存下来. <动手学深度学习>P ...

  3. 用PyTorch实现的李沐《动手学深度学习》,登上GitHub热榜,获得700+星

    晓查 发自 凹非寺  量子位 报道 | 公众号 QbitAI 李沐老师的<动手学深度学习>是一本入门深度学习的优秀教材,也是各大在线书店的计算机类畅销书. 作为MXNet的作者之一,李沐老 ...

  4. 《动手学深度学习》PyTorch版本

    Dive-Into-Deep-Learning-PyTorch-PDF 简介   本项目对中文版<动手学深度学习>中的代码进行整理,并参考一些优秀的GitHub项目给出基于PyTorch的 ...

  5. (d2l-ai/d2l-zh)《动手学深度学习》pytorch 笔记(2)前言(介绍各种机器学习问题)以及数据操作预备知识Ⅰ

    开源项目地址:d2l-ai/d2l-zh 教材官网:https://zh.d2l.ai/ 书介绍:https://zh-v2.d2l.ai/ 笔记基于2021年7月26日发布的版本,书及代码下载地址在 ...

  6. PyTorch 《动手学深度学习》学习笔记(Dive-into-DL-Pytorch)

    参考文章1:PyTorch版<动手学深度学习>PDF 版开源了 参考文章2:https://download.csdn.net/download/Dontla/12371960 文章目录 ...

  7. 李沐《动手学深度学习》PyTorch 实现版开源,瞬间登上 GitHub 热榜!

    点击上方"AI有道",选择"星标"公众号 重磅干货,第一时间送达 李沐,亚马逊 AI 主任科学家,名声在外!半年前,由李沐.Aston Zhang 等人合力打造 ...

  8. 【深度学习】李沐《动手学深度学习》的PyTorch实现已完成

    这个项目是中文版<动手学深度学习>中的代码进行整理,用Pytorch实现,是目前全网最全的Pytorch版本. 项目作者:吴振宇博士 简介   Dive-Into-Deep-Learnin ...

  9. 李沐《动手学深度学习》新增PyTorch和TensorFlow实现,还有中文版

    李沐老师的<动手学深度学习>已经有Pytorch和TensorFlow的实现了,并且有了中文版. 网址:http://d2l.ai/ 简介 李沐老师的<动手学深度学习>自一年前 ...

最新文章

  1. mycat schema.xml 配置文件详解
  2. 15.4.5 简化元组的使用
  3. BJUT算法设计与分析考试真题 无答案
  4. 在ubuntu系统中删除软件的三种最佳方法
  5. java类和对象的基础(笔记)
  6. 02-橄榄球 VS 软件
  7. 十大免费SSL证书:网站免费添加HTTPS加密
  8. C语言课程设计——工资管理系统
  9. Base64转MultipartFile
  10. vulnhub Pwned: 1
  11. java技术--报警通知及实现方式
  12. 手机USB共享电脑宽带的尝试过程,从失败到成功
  13. Web前端HTML+CSS全套(1~20)
  14. 国产自研芯片取得的进展,连外媒都认可了,ARM真怕了
  15. android 车载app怎么开发,Android开发智能车载App(2)---android paint和canvas自定义view
  16. 带你了解递归算法的时间复杂度
  17. RK3128-Android7.1-IR-深度剖析
  18. (Router)路由交换实验
  19. 怎么查看系统安装了mysql_如何查看系统安装的MySQL版本?
  20. 知到网课异彩纷呈的民族文化期末考试单元试题答案分享!

热门文章

  1. js中实现页面跳转的几种方法
  2. 借数字化东风带动营收增长,百融云创深耕金融SaaS成效如何?
  3. 使用HttpClient和Jsoup爬取京东商城关键字搜索的商品页面
  4. 【C#】C#调用Bartender模板打印,输出图片,PDF
  5. Roboware Studio详细安装教程 (ROS kinetic)以及简单使用
  6. 网易云音乐中间件改造
  7. 考系统集成项目管理工程师,报班还是自学?
  8. BIGGAN代码以及训练参数,超级清晰版(CIFAR10数据集生成)
  9. 概率论第二章知识点+错题总结
  10. 循环播放背景音乐 html,js背景音乐循环播放代码(多浏览器支持)