0%

Torch类具体说明--以剪枝模型为例

本节主要讲解剪枝模型,以此模型为基础顺带讲解一下torch中的一些常用方法,参数导出,梯度回溯,以及可视化

Torch类说明—以剪枝模型为例

Troch标准类格式01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def to_var(x, requires_grad=False):
"""
Automatically choose cpu or cuda
"""
if torch.cuda.is_available():
x = x.cuda()
return x.clone().detach().requires_grad_(requires_grad)


class MaskedLinear(nn.Linear):
def __init__(self, in_features, out_features, bias=True):
super(MaskedLinear, self).__init__(in_features, out_features, bias)
self.register_buffer('mask', None)

def set_mask(self, mask):
self.mask = to_var(mask, requires_grad=False)
self.weight.data = self.weight.data * self.mask.data

def get_mask(self):
return self.mask

def forward(self, x):
if self.mask is not None:
weight = self.weight * self.mask
return F.linear(x, weight, self.bias)
else:
return F.linear(x, self.weight, self.bias)

torch中的主类一般如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.linear1 = MaskedLinear(28 * 28, 200)
self.relu1 = nn.ReLU(inplace=True)
self.linear2 = MaskedLinear(200, 200)
self.relu2 = nn.ReLU(inplace=True)
self.linear3 = MaskedLinear(200, 10)

def forward(self, x):
out = x.view(x.size(0), -1)
out = self.relu1(self.linear1(out))
out = self.relu2(self.linear2(out))
out = self.linear3(out)
return out

def set_masks(self, masks):
# Should be a less manual way to set masks
# Leave it for the future
self.linear1.set_mask(masks[0])
self.linear2.set_mask(masks[1])
self.linear3.set_mask(masks[2])

def load_state_dict(self, state_dict, strict=True):
super().load_state_dict(state_dict, strict=False)

其实上述只是我们常用的一些方法重写,但是还有nn.Module还有很多可以提供自由使用的方法,不过正常不需要修改重写而已。我们可以看一下nn.Module中的各个类,输入如下:

1
dir(MLP())

得到结果如下:,可以看到其实dir自身也是类的方法,其可以显示类中的所有方法名,可以看到init中的liner1、2、3也作为了其独立的方法,其中还有常见的forward,其主要用于拼接一些方法进行前向推理,还有load_state_dict方法,其是用于参数加载的,以备模型迁移时候,直接读入权重系数,需要改方法时候,重写一下就好。还有比较常用的train和eval方法,因为有些内容,比如dropout,在训练时用,但测试时不用,所以需要通过一些方法来使一些方法(比如在init中定义的dropout可以理解为一个方法,像linear1就会加入到该类的方法里了)无效(理解为跳过),或有效,所以需要train方法和eval方法,代表训练和推理模式,使得一些方法有效或无效。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
['T_destination',
'__annotations__',
'__call__',
'__class__',
'__delattr__',
'__dict__',
'__dir__',
'__doc__',
'__eq__',
'__format__',
'__ge__',
'__getattr__',
'__getattribute__',
'__gt__',
'__hash__',
'__init__',
'__init_subclass__',
'__le__',
'__lt__',
'__module__',
'__ne__',
'__new__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__setattr__',
'__setstate__',
'__sizeof__',
'__slotnames__',
'__str__',
'__subclasshook__',
'__weakref__',
'_apply',
'_backward_hooks',
'_buffers',
'_call_impl',
'_forward_hooks',
'_forward_pre_hooks',
'_get_name',
'_load_from_state_dict',
'_load_state_dict_pre_hooks',
'_modules',
'_named_members',
'_non_persistent_buffers_set',
'_parameters',
'_register_load_state_dict_pre_hook',
'_register_state_dict_hook',
'_replicate_for_data_parallel',
'_save_to_state_dict',
'_slow_forward',
'_state_dict_hooks',
'_version',
'add_module',
'apply',
'bfloat16',
'buffers',
'children',
'cpu',
'cuda',
'double',
'dump_patches',
'eval',
'extra_repr',
'float',
'forward',
'half',
'linear1',
'linear2',
'linear3',
'load_state_dict',
'modules',
'named_buffers',
'named_children',
'named_modules',
'named_parameters',
'parameters',
'register_backward_hook',
'register_buffer',
'register_forward_hook',
'register_forward_pre_hook',
'register_parameter',
'relu1',
'relu2',
'requires_grad_',
'set_masks',
'share_memory',
'state_dict',
'to',
'train',
'training',
'type',
'zero_grad']

下面仔细分析一下类:其中类中的self是认为实例自身的意思,__init__则是初始化方法的意思,实例创建的时候自动调用__init__方法,而super的意思可以理解为父类的自身,因为MLP继承了父类的所有方法,而初始化你重写了,但是你的初始化中并未对所有的方法初始化(有些方法要初始化),所以需要调用父类的__init__进行初始化,super().__init__()就是调用父类的初始化函数,但是需要指定对象,即父类的初始化方法是对谁进行初始化的,所以就super(MLP, self).__init__(),前面的MLP代表子类,self代表实例自身,即对该子类的实例进行初始化。

1
2
3
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()

调用父类初始化函数初始化对象举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:
def __init__(self,name,gender):
self.name = name
self.gender = gender
def printinfo(self):
print(self.name,self.gender)

class Stu(Person):
def __init__(self,name,gender,school):
super(Stu, self).__init__(name,gender) # 使用父类的初始化方法来初始化子类
self.school = school
def printinfo(self): # 对父类的printinfo方法进行重写
print(self.name,self.gender,self.school)

if __name__ == '__main__':
stu = Stu('djk','man','nwnu')
stu.printinfo()

参考链接:(29条消息) 解惑(一) ——- super(XXX, self).init()到底是代表什么含义_奋斗の博客-CSDN博客

再来看一下后续代码,为什么又用MaskedLinear这个类呢?__init__中,一般我们希望只定义方法或者变量,所以我们写了个类作为方法linear1。

1
2
3
4
class MLP(nn.Module):
def __init__(self):
super(MLP, self).__init__()
self.linear1 = MaskedLinear(28 * 28, 200)

MaskedLinear子类01

下面看一看MaskedLinear这个类,其是继承了Liner类,通过supe(MaskdLinear,self).__init__进行了初始化,注意这里初始化传入了输入单元数和输出单元数。然后定义了个变量叫mask,这里需要注意,其不是用self.mask定义变量的,而是self.register_buffer()定义的,self.register_buffer本质上定义的不是参数,而只是留出了一个缓冲区,缓冲区名为mask,初始值是None,这个在之前博客总结说过,即尚未定义的意思。torch中,一般参数存储都是存储成orderedDict形式的(所以模型的参数往往都是在一个字典里),而orderedDict包括模型的各类module的参数,即nn.Parameter,另一种就是buffer,buffer很特殊的特征就是不会得到更新,即视为一个常数了,而不是变量。参考链接:

(29条消息) pytorch 中register_buffer()_shuijinghua的博客-CSDN博客_register_buffer

1
2
3
4
class MaskedLinear(nn.Linear):
def __init__(self, in_features, out_features, bias=True):
super(MaskedLinear, self).__init__(in_features, out_features, bias)
self.register_buffer('mask', None)

再看如下:

1
2
3
def set_mask(self, mask):
self.mask = to_var(mask, requires_grad=False)
self.weight.data = self.weight.data * self.mask.data

to_var类

又嵌套了to_var类,来看一下to_var类,代码如下,如果GPU可以使用的话,那么将数据放到GPU上,返回的时候,不是直接返回x,而是使用了一系列方法。在np中,如果直接a=b的话,其实a和b是共享内存的,但如果进行切片的话,则是两个内存空间,由于np是为了数据处理,复杂度远不如神经网络大,所以复用性不需要那么高,但是torch中,由于数据太多,所以复用就很关键,所以不管是直接赋值还是切片,都是指向同一个内存单元,所以需要开辟新空间的话,就需要用clone这个方法了。

1
2
3
4
def to_var(x, requires_grad=False):
if torch.cuda.is_available():
x = x.cuda()
return x.clone().detach().requires_grad_(requires_grad)

下面来说明一下clone和detach方法,我们可以在jupyter中一下clone和detach的含义,创建一个torch.tensor变量a,然后a.clone?和a.detach?即可查看,detach说明结果如下,可以从描述文档中看出,detach是从来不用梯度的,即无梯度这个属性,即数据共享,但脱离了计算图;clone其实是可以追溯梯度的,后面再说。值得注意的是:clone是开辟了一个新内存空间,保存梯度信息,但是detach其实是共享了a的data内存,但是丢弃了梯度(为什么不说完全共享内存呢?因为torch中需要梯度等等信息,所以一个tensor变量并不仅仅只有存储数据的空间,还会有梯度空间等等(所以tensor变量有data方法,用于只取出data),而此处detach只是共享了data空间,即a.data,所以a.detach的指针和a未必相同,即id(a) 和id(a.detach())可能不一样

1
2
3
4
5
6
7
detach--Docstring:
Returns a new Tensor, detached from the current graph.
The result will never require gradient.

clone--Docstring:
clone(*, memory_format=torch.preserve_format) -> Tensor
See :func:`torch.clone`

如下代码测试:

1
2
3
4
5
6
7
8
a=torch.tensor([1.0,2,3], requires_grad=True)
b=a
c=a.clone()
d=a.detach()
print(a)
print(b)
print(c)
print(d)

得到结果如下,可以看出,require_grad=True即可追溯计算梯度,clone的结果保留了梯度结果,而detach却没有。grad_fn=<CloneBackward>,表示clone后的返回值是个中间变量,因此支持梯度的回溯

1
2
3
4
tensor([1., 2., 3.], requires_grad=True)
tensor([1., 2., 3.], requires_grad=True)
tensor([1., 2., 3.], grad_fn=<CloneBackward>)
tensor([1., 2., 3.])

如下可以看出detach是共享data内存的,而clone不是

1
2
3
4
5
6
7
8
9
10
a=torch.tensor([1.0,2,3], requires_grad=True)
b=a
c=a.clone()
d=a.detach()
d[0]=5
c[0]=100
print(a)
print(b)
print(c)
print(d)

输出如下:

1
2
3
4
tensor([5., 2., 3.], requires_grad=True)
tensor([5., 2., 3.], requires_grad=True)
tensor([100., 2., 3.], grad_fn=<CopySlices>)
tensor([5., 2., 3.])

上述clone的梯度回溯是什么意思呢?就是对该clone的值的求导结果,值是累加在原值a上的,如下:

1
2
3
4
5
6
7
8
9
10
import torch
a = torch.tensor(1.0, requires_grad=True)
a_ = a.clone()
y = a**2
z = a ** 2+a_ * 3
y.backward()
print(a.grad)
z.backward()
print(a_.grad)
print(a.grad)

结果如下,可以看出y.backward()方法后,就会往回追溯梯度,得到的2结果存入在a的梯度中,但是z追溯梯度的时候,a_无梯度,是因为其可以梯度结果放在了原值上,此时累计2+2+3得到7,如果没有y.backward()这个句,结果就是5(由于梯度会进行累加,所以卷积神经网络搭建的时候,要进行清零操作)即如果原数据的requires_grad的属性是True,则clone后也是True,只是梯度信息累加在了原数据上,可以通过a.requires_grad来或得是否进行梯度运算

1
2
3
tensor(2.)
None
tensor(7.)

如果原数据的requires_grad的属性是False,则clone后设置为True,那么梯度信息只能留在clone后的数据上,如下:

1
2
3
4
5
6
7
8
import torch
a = torch.tensor(1.0)
a_ = a.clone()
a_.requires_grad_() #require_grad=True
y = a_ ** 2
y.backward()
print(a.grad) # None
print(a_.grad)

输出如下:

1
2
None
tensor(2.)

注意:其实view其本质只是将数据拉成一个维度的,而torch中只是可视化不同,内存依旧是共享的原数据data的。

参考链接:(29条消息) PyTorch中的clone(),detach()及相关扩展_Breeze-CSDN博客

而a.requires_grad_()是将梯度设置为True,也可以传入True/False,比如:a.requires_grad_(True),不写默认为True,返回的依旧是a,不过梯度是否纪录这个属性被修改了。所以回到to_var类,其返回的就是clone后的一个新空间然后又detach使其脱离计算图,其实此时不需要再设置require_grad_了,因为其已经没有梯度了,再False没有意义。

1
2
3
4
def to_var(x, requires_grad=False):
if torch.cuda.is_available():
x = x.cuda()
return x.clone().detach().requires_grad_(requires_grad)

MaskedLinear子类02

继续MaskedLinear类,所以如下就是传入一个mask,但是clone一个获取新内存同时通过detach使其脱离计算图。(因为剪枝的mask我们是希望不随梯度进行改变的)。如下self.weight就是得到权重,但是这个权重是加上了梯度属性的,self.weight则得到了简单的数据,其和self.state_dict()[“weight”],state_dict()由上述知道其实类的一个方法,主要是用于获取训练好的模型后的参数,用于保存的,所以self.state_dict()[“weight”]直接就是不带梯度的。所以self.state_dict()[“weight”].data这样是否有这个.data都无所谓,因为梯度属性为Fasle,就不需要空间存储梯度。如下就是将参数乘上掩码的过程,为了得到稀疏化的参数。值得注意的是:可以看到nn.Module是weight方法的,weight方法是nn.Linear的方法,因为线性模型只有weight和bias,所以还有bias属性。

1
2
3
def set_mask(self, mask):
self.mask = to_var(mask, requires_grad=False)
self.weight.data = self.weight.data * self.mask.data

该类的forwar也是判断是否进行掩码操作的。

Troch标准类格式02

继续回到MLP类,如下,这个inplace我们似乎不太常用,这个属性作用是什么呢?其默认是False,即输入一个值时候,默认为是值传递,即返回的数值是一个新的内存空间,而True的话,则是地址传递,可以通过打印id(a)来查看,发现其输入输出是同一个地址,即共享内存了,会修改输入的值。

1
self.relu1 = nn.ReLU(inplace=True)

然后下面如下,其实forward函数是类实例化后,输入参数自动执行的方法,其实本质是自动执行了__call__,然后该魔法函数中调用了forward。在该函数中,上述clone那写了,view只是改变可视化,内存依旧与之前的共享。

1
2
3
4
5
6
def forward(self, x):
out = x.view(x.size(0), -1)
out = self.relu1(self.linear1(out))
out = self.relu2(self.linear2(out))
out = self.linear3(out)
return out

然后看下一个方法,这个方法显然是给linear方法设置参数的,linear方法即上面的MaskLinear类,其通过set_mask来设置掩码。

1
2
3
4
5
6
def set_masks(self, masks):
# Should be a less manual way to set masks
# Leave it for the future
self.linear1.set_mask(masks[0])
self.linear2.set_mask(masks[1])
self.linear3.set_mask(masks[2])

最后是加载权重的方法,如下。strcit的参数意思是,如果之前的网络时两层的,但是现在是3层的,那么直接加载会报错,因为两个模型不一致,但是将这个参数改为False,即表示能用多少用多少,即取两层作为当前模型的前两层的权重。

1
2
def load_state_dict(self, state_dict, strict=True):        
super().load_state_dict(state_dict, strict=False)

模型搭建好了后,下面是训练函数的定义,首先得到一个实例化的model后,调用方法model.train(),这个方法是使得一些训练的方法生效(比如dropout),使得一些推理的方法失效,与之对应的是model.eval(),其代表推理模式。然后for循环给批量数据,每次需要把数据送到GPU里(这里实例化的模型传入之前应该就是已放在GPU上了),然后梯度归零,因为梯度会进行累加,所以要进行清零。后面就是正向传播得到输出,计算损失,再对损失进行求反向梯度,然后优化器优化(各类梯度下降,SGD等等)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def train(model, device, train_loader, optimizer, epoch):
model.train()
total = 0
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = F.cross_entropy(output, target)
loss.backward()
optimizer.step()

total += len(data)
progress = math.ceil(batch_idx / len(train_loader) * 50)
print("\rTrain epoch %d: %d/%d, [%-51s] %d%%" %
(epoch, total, len(train_loader.dataset),
'-' * progress + '>', progress * 2), end='')

推理函数如下,较为简单,讲解略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def test(model, device, test_loader):
model.eval()
test_loss = 0
correct = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
test_loss += F.cross_entropy(output, target, reduction='sum').item() # sum up batch loss
pred = output.argmax(dim=1, keepdim=True) # get the index of the max log-probability
correct += pred.eq(target.view_as(pred)).sum().item()

test_loss /= len(test_loader.dataset)

print('\nTest: average loss: {:.4f}, accuracy: {}/{} ({:.0f}%)'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))
return test_loss, correct / len(test_loader.dataset)

训练完成后,要考虑剪枝,如下,其中第一个参数是模型,第二个参数是需要剪枝的比例,比如0.9,即剪百分之九十的weight,即使得百分之90的weight为0。首先遍历参数,如果size长度不是1则需要裁剪(因为bias是一维的,即shape一维,所以bias不需要裁剪),np.percentile(weight, pruning_perc)这个第一个参数传入一个数组,第二个参数传入0-100(代表0%-100%)的数a,返回的是一个值,比如传入60,返回的这个值超过了数组中百分之60的数,来举个例子,比如传入的数组是[[1,4,6],[2,5,7]],百分数参数是70,则把数组拉平从小到大排序,即1,2,4,5,6,7,那么百分之70在哪呢?numpy中下标是0开始到5,所以50.7=3.5,所以得到的下标应该是在3.5这,那么3.5不是整数怎么办?进行线性插值,所以结果就是5+(6-5)\0.5=5.5。得到这个值即可通过判断data>该值,得到0-1变量,即掩码。然后第二个for即是生成0-1变量的掩码,记得得变成float类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def weight_prune(model, pruning_perc):
'''
Prune pruning_perc % weights layer-wise
'''
threshold_list = []
for p in model.parameters():
if len(p.data.size()) != 1: # bias
weight = p.cpu().data.abs().numpy().flatten()
threshold = np.percentile(weight, pruning_perc)
threshold_list.append(threshold)

# generate mask
masks = []
idx = 0
for p in model.parameters():
if len(p.data.size()) != 1:
pruned_inds = p.data.abs() > threshold_list[idx]
masks.append(pruned_inds.float())
idx += 1
return masks

所有函数都定义好了,下面看一下主函数,torch.manual_seed(0)函数是设定随机数种子,后面生成的随机数都是固定的,即不会变化(ramdom.seed类似),然后使用torch.utils.data.DataLoader读取MNIST数据集,如果没有该数据集,则将download设置为True,’../data/MNIST’即为下载的路径,同时也是MNIST数据集的路径,..和linux中的cd ..一样,代表路径回退一级的意思。在’../data/MNIST’下,还会有一个交MNIST的文件夹,里面有raw、processed文件夹,datasets.MNIST这个函数会自动识别的,transform参数代表数据预处理,即变为tensor然后还进行归一化(均值和方差)。torch.utils.data.DataLoader的batch_size是设定batch大小,shuffle代表是否打乱顺序。训练完成后测试,然后保存模型参数:torch.save(model.state_dict(), ‘not-pruned.ckpt’),然后通过copy库的deepcopy来深度拷贝这个模型,作为剪枝,同时测试剪枝后的准确率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
def main():
epochs = 1
batch_size = 64
torch.manual_seed(0)

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data/MNIST', train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data/MNIST', train=False, download=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=1000, shuffle=True)

model = MLP().to(device)
optimizer = torch.optim.Adadelta(model.parameters())

for epoch in range(1, epochs + 1):
train(model, device, train_loader, optimizer, epoch)
_, acc = test(model, device, test_loader)

torch.save(model.state_dict(), 'not-pruned.ckpt')
print("\n=====Pruning 60%=======\n")
pruned_model = deepcopy(model)
mask = weight_prune(pruned_model, 60)
pruned_model.set_masks(mask)
test(pruned_model, device, test_loader)
torch.save(pruned_model.state_dict(), 'pruned.ckpt')

return model, pruned_model

剪枝完成后,需要考虑可视化问题,如下,其中model.modules是一个迭代器,可以遍历所有模型的所有子层(init中定义的层),然后再通过hasattr函数来判断该层是否有weight属性来判断是否是全连接层,进行绘图,plt.subplot(131)代表1行3列的绘图板,在第一个格子里绘图,plt.subplot(132),表示在第二个格子里绘图。结果可以发现剪枝百分之60后,准确率只掉了一个点,但是参数化绝对稀疏了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from matplotlib import pyplot as plt
def plot_weights(model):
modules = [module for module in model.modules()]
num_sub_plot = 0
for i, layer in enumerate(modules):
if hasattr(layer, 'weight'):
plt.subplot(131+num_sub_plot)
w = layer.weight.data
w_one_dim = w.cpu().numpy().flatten()
plt.hist(w_one_dim[w_one_dim!=0], bins=50)
num_sub_plot += 1
plt.show()
plot_weights(model)
plot_weights(pruned_model)

输出如下:

oehbND.png
oehHAO.png

下面具体说一下modules方法,测试代码如下:

1
2
3
4
5
6
7
8
modules = [module for module in model.modules()]
#print(modules)
count = 1
for module in model.modules():
if hasattr(module, 'weight'):
print(count,"有weight属性")
print(count,module)
count = count+1

输出如下,可以看出,迭代器返回的第一个是MLP大类的所有方法层,后续开始返回单独子层,上面也说了MLP是继承nn.Module的,dir可以看出其是没有weight方法的,但是Linear是有的,所以通过判断是否有weight属性来判断是否是全连接or卷积层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1 MLP(
(linear1): MaskedLinear(in_features=784, out_features=200, bias=True)
(relu1): ReLU(inplace=True)
(linear2): MaskedLinear(in_features=200, out_features=200, bias=True)
(relu2): ReLU(inplace=True)
(linear3): MaskedLinear(in_features=200, out_features=10, bias=True)
)
2 有weight属性
2 MaskedLinear(in_features=784, out_features=200, bias=True)
3 ReLU(inplace=True)
4 有weight属性
4 MaskedLinear(in_features=200, out_features=200, bias=True)
5 ReLU(inplace=True)
6 有weight属性
6 MaskedLinear(in_features=200, out_features=10, bias=True)

总结:a代表tensor变量,C代表一个类

a.clone机制

a.detach机制

a.requires_grad_

a.data

id(a)#打印指针

nn.Relu的inplace参数

C.state_dict()#获得含所有参数的字典,无梯度信息,且不是迭代器,其他parameters等都是可迭代对象

C.parameters()#获得所有参数,可以list(C.parameter)变成迭代器,也可以for,得到的参数含梯度信息。

C.weight#获得网络权重,nn.Linear有这个方法,还有bias方法,nn.Module没有,

np.percentile#返回超过百分之a的数。

注:torch中梯度等需要数据是float,数据生成的时候,最好都是以float存储,至少tf中都不会自动类型转换,可能怕存储爆炸?torch测试了是会自动数据转换,但是还是最好float一下。

- - - - - - - - - - - - - - 本文结束啦,感谢您的观看 - - - - - - - - - - - - - -