torch库的使用

本文目录

torch库的使用(pytorch框架):
在pytorch中,FloatTensor是基本的数据格式,等同于ndarray在numpy中的地位。
另一种常用格式是变量Variable,常用于计算图。
FloatTensor.view:与Matlab的reshape类似,对矩阵进行不同维度间的变换
x.view(2, 12) 把x折成2×12的矩阵
x.view(2, -1) 另一种写法,-1表示此维度可以推断出来。由x共24个元素,会自动生成2×12矩阵
在view的时候,有时会因为一连两个元素相同,python认为这个序列是不连续的,后面会出问题从而报错,所以使用x.contiguous()它进行连续化,连续化后值不变。
Variable(a)可以把FloatTensor转换成变量,而a.data又可以把变量转换为Tensor.
a[a:gt(3)] 返回a中大于3的元素
Variable.backward():当计算图建立起来之后,利用backward方法可以反向求梯度。标量可以不带参数,默认为backward(torch.Tensor([1.0])),表示步长为1。因为我们求梯度求出来的只是导数,后面要梯度下降的时候并不能直接减去一个导数,而是要减去导数乘以步长的积(),也即走一小步。对向量或矩阵求梯度则需要给出一个步长向量或矩阵,与微分变量tensor同维度()
Variable.grad:在因变量调用了backward()之后,自变量就会有一个grad属性,此即为的值,可以拿来调整各个权重
在网络的forward前向传播方法定义好之后,backward会自动地生成,不用我们自己再写一遍。
注意在测试阶段, 我们是不需要计算梯度的, 也即不需要保留运算过程中的神经元激活模式, 因此我们要在测试代码的循环前面加上一句with torch.no_grad(): 来让pytorch不要计算梯度, 这样可以少占用很多显存.
autograd模块的用法
自定义一个继承于torch.autograd.Function的类,可以做一些别样的 forward操作,同时自定义backward方法,下面是一个例子:

class Render(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, y):
    	z = y.clone()
        return z
    
    @staticmethod
    def backward(ctx, output_grad):
        grad = output_grad + 1
        return None, grad

forward的输入可以是tensor也可以是其他奇奇怪怪的东西,比如list,你可以在forward函数里面读取图片,然后返回一个Image tensor。只要forward的输入中有一个是requires_grad=True的,forward的输出就会是requires_grad=True的。输出的tensor的grad_fn就是RenderBackward了,就算forward的输入跟输出没有直接关系,我们也可以通过这种方法给它们架起梯度反传的桥梁。backward的时候,输入自然是z的梯度,而输出个数应与forward的输入参数个数相同且位置上一一对应,不要梯度的要给None,不能直接省略。

autograd.Function文档
单个输出值对上一层的导数是一个m维向量v,上一层参数对上上层参数的Jacobian矩阵是个mxn矩阵J,那么根据链式法则,输出对上上层参数的导数就是 J T ⋅ v J^T·v JTv,依然是一个向量,维度为n,继续往上一层传播。
在这里插入图片描述
结合上图,y.backward(v)中的v其实是表示上层参数对y的导数,这个语句将执行类似 J T ⋅ v J^T·v JTv的操作,把梯度往前传。(注:tensor.backward()torch.autograd.backward()的参数表是不一样的,后者有多一个grad_tensors参数,作用不明)

设置误差计算的方法:

from torch import nn
criterion = nn.MSELoss() # 采用均方误差
criterion = nn.CrossEntropyLoss() # 采用交叉熵损失

等等
接着计算误差:loss = criterion(output, target) ,loss就可以用来backward了. 每个batch的输出一起进入criterion, 输出只是一个数而不是一个向量, 它应该是该batch中各个样本的loss的平均值.
Pytorch的各种内置损失函数
如果刚好没有满足要求的, 想自定义损失函数也是可以的, 只需这样做:
定义自定义损失函数: l o s s = w 1 ∗ l o s s 1 + w 2 ∗ l o s s 2 loss=w_1∗loss_1+w_2∗loss_2 loss=w1loss1+w2loss2

class myloss(nn.Module):
    def __init__(self, w1, w2):
        super(myloss, self).__init__()
        self.w1 = w1
        self.w2 = w2
        
    def forward(self, output1, target1, output2, target2, mse):#mse:最小平方误差函数
        loss = self.w1 * mse(output1, target1) + self.w2 * mse(output2, target2)
        return loss

只要是从nn.Module中继承而来, 然后有一个forward方法, 就可以作为自定义损失函数啦.
普通的torch.LongTensor和FloatTensor没有梯度属性,需要先将它们转换为变量才能进行计算loss:如图像的标签预先转换:labels = Variable(labels);图像在输入网络前预先转换:inputs = Variable(inputs),这样outputs = net(inputs)输出的结果才会是变量,才能求梯度

算出loss之后就可以进行反向传播loss.backward(),然后更新参数了:optimizer.step()。如果想要只训练部分层,可以采用如下方法:

for param in model0.parameters():
    param.requires_grad = False
optimizer = optim.SGD(
            filter(lambda p: p.requires_grad, model.parameters()),  # 记住一定要加上filter(),不然会报错
            lr=0.01, weight_decay=1e-5, momentum=0.9, nesterov=True)

另外一种实现训练差异的方法是对不同的层设置不同的学习率,详见: finetune冻结层操作 + 学习率超参数设置
把参数的requires_grad属性设为False,这个参数就不会更新了,model.train()model.eval()不会影响这个属性的取值。输入数据因为从头到尾流经整个计算图,所以不允许中间的requires_gradFalse,但参数不一样,参数只要对自己负责就可以了,所以可以第一个module设为True,第二个是False,第三个再True,以此冻结第二个module。
然后要注意一下BatchNorm层,它的running mean跟running average即使不要梯度也会更新,如果要让它不更新,可以这样处理:

from torch.nn.modules.batchnorm import _BatchNorm
for m in model.modules():
  if isinstance(m, _BatchNorm):
      m.momentum = 0.0  # 默认值是0.1

m.momentum = 0.0换成m.eval()也是可以的,只是这样的话我们每次测试后继续训练一般会调用model.train(),又会还原回来,就又得再手动设一遍bn的eval(),不是很方便。

pytorch构建的计算图是动态图,为了节约内存,所以每次一轮迭代完也即是进行了一次backward()之后计算图就在内存被释放,因此如果不执行loss.backward(),计算图就不会释放,这样会导致out of memory报错。而如果你需要进行多次backward(),则需要在非最后一次的backward()中添加一个retain_graph=True参数来保留计算图,然后最后一次backward()像平常一样调用,让计算图得以释放。
故在调试时可以不step(),但一定要backward()参考链接:pytorch中backward()函数详解
注意到我们有时候会有仅对模型的一部分进行参数更新的需求,如果直接通过“在optimizer初始化时只传入部分参数”来实现,会有这样的隐患:在optimizer.zero_grad()时,只有要更新的参数梯度被清空,剩下的参数梯度并没有清空,它们会一直累加,举个例子,y=w2(w1(x)),如果optimizer中只有w1,那么在不做其他处理的情况下w2的梯度会一直累积(最容易看到现象的例子是位于模型最后一层的FC层的bias,通常情况下它的梯度一直是1,如果没有被清空,这个梯度将每个iter增加1)。由于w2本身没在optimizer中,不会更新参数,且对于参数的梯度不会往下游传,因此梯度的累加本身不会对模型的产生任何影响(当然也可能存在某些未发现的潜在问题),从参数值上看等效于在optimizer中将w2lr设置为0.0。如果想要避免梯度累加的情况,目前想到的解决方案是用另一个optimizer囊括其余无需更新的参数,这个optimizer不进行step(),只进行zero_grad(),目的就是清除第一个优化器没有清零的梯度。
综上所述,冻结一个模块有几种方法:
1.optimizer给出0.0的学习率;
2.第二种是optimizer中不包含该模块的参数,但这会让该模块本身的梯度产生累加,虽然目前暂时没有看到出现问题;
3.requires_grad=False,注意不能直接module.requires_grad=False,它不能直接传回自己的submodule,但又不会报错,所以一旦写错不容易发现。要在for m in module.parameters():循环里面执行m.requires_grad=False
不管用上述哪一种方法,都只能冻结参数,无法触及BN层的running_meanrunning_var,所以要想真正冻结所有参数,还需要另外进行fix_bn的操作,详见前几段的代码。

optimizer在初始化时传入的params必须是个可迭代对象,对于模型而言直接model.parameters()就可以了,如果是训练对抗样本,需要对输入进行反向传播的话,要这样写(注意到列表也是可迭代对象来着):

# 需要更新参数的记得加上requires_grad=True
render_inputs_var = torch.autograd.Variable(render_inputs_tensor, requires_grad=True)
optimizer = torch.optim.Adam([{'params': render_inputs_var, 'name': 'new-added'}], lr=...)

试过在外面多套了一层:

render_inputs_var = torch.autograd.Variable(render_inputs_tensor, requires_grad=True)
params = iter([ torch.nn.Parameter(render_inputs_var) ])
optimizer = torch.optim.Adam([{'params': params, 'name': 'new-added'}], lr=...)

结果backward的时候render_inputs_var有梯度,但是不会更新。
在对图片等张量求梯度时候, 发现了关于requires_grad的用法的一些点:
规则1: 只有叶子结点才能修改requires_grad参数. 原因: 以计算图a->b->c为例, 如果a为true, 为了计算a的梯度, bc必须也为true, 不允许修改为false, 因为pytorch中设定为如果计算图的前面的节点有要计算梯度的, 就不能再去改动其后面的结点的requires_grads属性. 但如果a是false, b是可以在后面用一行语句设置为True的, 不是说一定要是整个运算关系图的起始点.
规则2: 其实这可能不应该算是一个规则, 应该算是一个一开始以为是bug的点. 现象是这样的, 首先对一个变量设为true, 然后对其进行修改: x=x.view(1,2,3,4), 并加上一句requires_grad=True, 最后发现x.grad=None. 究其原因, 其实就是我们被自己用同一个变量名生成的新变量混淆了. 一开始设置x为true, 接着对x进行view操作, 得到了一个新的x, 此时这个x是计算图中的第2个节点, 它只是一个中间变量, 真正的叶子结点是一开始定义的那个x, 然后说一下为什么加了requires还是不行, 根据规则1, 本来是无法修改叶子结点往后的requires属性的, 是因为新的x本就已经是true了(因为它的前一个结点是True, 因此它也自动为True), 而作为中间变量是不会保存backward的时候传过来的梯度的, 因此最后的x.grad一定是None. 而最开始的那个x确实是有梯度的, 只是我们用同一个变量名覆盖掉了它的指针, 现在访问不到它了. 如果把上面的语句改成y=x.view(1,2,3,4), backward之后x是有梯度的, y没有, 这点在实验中得到了验证.

nn.PixelShuffle()函数对矩阵进行重排,将尺寸为(, r 2 C r^2C r2C, H, W)的矩阵变换成为尺寸为(, C, rH, rW)的矩阵. “用于实现高效的子像素卷积,步长为1/r”,也可用于用多个低分辨率(尺度)层变换成一张高分辨率图像.

记torch.nn.functional为F,F.affine_grid函数和F.grid_sample函数常用于空间变换网络STN.
Grid = F.affine_grid(theta, size) theta是一批映射矩阵(Nx2x3),size是变换后图像的尺度(NxCxHxW),其输出grid是一个(NxHxWx2)的矩阵,grid[n, i, j, 0] (记为p)与grid[n, i, j, 1] (记为q)代表的是目标图片第i行j列的像素取用原图像的第p行第q列的像素值。变换前后的H跟W是可以不同。
利用上面生成的映射矩阵,我们可以通过F.grid_sample(x, grid)函数,通过双线性插值法得到变换后的图像(因为grid中的值通常不会是整数,所以一个目标图像的像素点的值需要原图像的多个像素点综合起来求解,以及可能会出现一些空白区域,因此需要插值操作)。
需要注意的一点是,theta表示的是从目标图像到原图像的仿射变换矩阵,因为这个矩阵是为了服务于“对目标图像的每一个位置,都找到一个原图像中的像素点与其对应”的需求,所以要反过来的变换矩阵,这点需要注意一下。
关于仿射变换矩阵以及grid跟sample的原理
实现例子
另外, 这里用到的theta的角度跟我们平时说的theta的逆时针方向变化的角度不一样, 测试了一下pytorch是顺时针(clockwise)方向变化的角度, 这点也需要注意一下.

常用函数

Tensor就是多维的矩阵,可以用torch.FloatTensor()torch.IntTensor()等方法创建。
Tensor.squeeze(dim)将输入张量形状中的1 去除并返回。 如果输入是形如(A×1×B×1×C×1×D),那么输出形状就为: (A×B×C×D),dim缺省为None,当给定dim时,那么挤压操作只在给定维度上。例如,输入形状为: (A×1×B), squeeze(input, 0) 将会保持张量不变,只有用 squeeze(input, 1),形状会变成 (A×B)。
Tensor.unsqueeze(dim) 返回一个新的张量,对输入的指定位置插入维度 1
Tensor.permute(dims) 交换维度顺序,dims是个int向量,指明各维度的摆放位置
维度操作: cat、stack、tranpose、permute、unsqeeze
Tensor.repeat(4,2) 与matlab中的repmat类似
Tensor的乘方也是**

在建立好网络模型model(它的父类是nn.Module)之后,model.train()将模型设定在训练状态,model.eval()则将模型切换回测试状态。至于代码实现,eval()实际上是执行了model.train(mode=False),所以traineval用的其实是同一份代码,同时也推知model.train(mode=False)表面看是训练状态,实际效果却是测试状态。参考链接在此
nn.Module.register_buffer() 函数用于给module添加一个持久的缓冲器buffer,常用于保存模型中的中间变量(不是参数),如批归一化中的’running_mean’. 用法是register_buffer(name, value),在buffer声明之后,便可以直接通过该变量名来调用该变量了(不需要加self.)。
tensor.random(a, b) 会用[a, b)间的随机整数填充整个张量

Tensor.select函数用于选出某个维度里的某个张量矩阵,用法是select(dim, index) ,效果等价于x[:, index, :],例如:
x = torch.ones([2, 3, 4])
x.select(2,3)将返回张量x[:, :, 3]
Tensor.narrow函数等效于:切片符,用法是narrow(dimension, start, length),例如:
x = torch.ones([2, 3, 4])
x.narrow(2, 1, 2)将返回张量x[:, :, 1:3], 尺寸为[2, 3, 2]
Tensor.clone()克隆一个张量
Tensor.fill_(value)给张量里的元素填充同一个值
Tensor.random(a, b) 会用[a, b)间的随机整数填充整个张量
Tensor.resize_(dim1, dim2,...) 调整张量的结构
Tensor.view(dim1, dim2,...) 输出一个新的张量,是原张量调整结构后得到的,可以用-1代替某个维度(resize_不行)
Tensor.item()方法可以把一个单元素Tensor转换成python的Float类型. 当我们在累计测试误差以及正确率等时,因为这个一般是直接correct=0,是Float,而torch计算出来的误差等数值都是Tensor,所以需要这一步转换.
Tensor.expand()函数有点类似np.tile或是matlab的repmat函数,用法是a.expand(new_dim1, new_dim2, ...),没有发生变换的维度的数据将保持不变,变换了的维度将被复制填充。
b.view_as(a)方法可以把b按照a的shape进行转换

torch.rsqrt(a) 返回平方根的倒数
torch.mean std prod sum var tanh max min(input) 返回均值 标准差 累乘 求和 方差 双曲正切 最大 最小值
torch.equal(Tensor1,Tensor2)两个张量进行比较,如果相等返回true,否则返回false
torch.bmm(a, b) 执行两个张量之间的批矩阵间乘积( batch matrix-matrix product),记a.shape=[ B a ,   H a ,   W a B_a,\ H_a,\ W_a Ba, Ha, Wa],b.shape=[ B b ,   H b ,   W b B_b,\ H_b,\ W_b Bb, Hb, Wb] ,则应满足 B a = B b ,   W a = H b B_a=B_b,\ W_a=H_b Ba=Bb, Wa=Hb
torch.topk()函数用于返回张量中最大/最小的k个值,用法是topk(input, k, dim=None, largest=True, sorted=True, out=None)。返回一个(values, indices)元组
torch.cat((a, b), dim) 把张量a跟张量b在维度dim上连接起来
torch.save(model, model_out_path)可以将训练好的模型保存下来,一般保存为.pt文件
torch.load(filename)方法可以导入之前保存下来的模型
torch.full(size, fill_value)根据size所给的尺寸,生成一个元素均填充为fill_value的张量
torch.max()返回最大值,如果不传dim,则只会返回一个最大值标量,而传入dim之后,除了返回一个最大值数组,还会多返回一组最大值对应的索引数组
torch.gather函数:gather(input, dim, index, out=None) 用于在dim维度,根据index中的下标值,对input进行寻址并输出,输出output, input跟index的尺寸都是相同的。
Tensor类里面有一些函数,除了a.func外还有一个很相似的a.func_,后者称为In-place版本,表示对张量a进行该操作,修改了a的值;前者是利用a进行运算,然后把结果赋给另一个张量,所以它们的调用方式不一样,分别是b = a.func(p)a.func_(p)
PyTorch中Tensor的查找和筛选:index_select, where, equal等
torch.histc()函数可以计算直方图,便于我们观察模型输出的分布:

# a = torch.arange(0.0, 100.0) / 20 + 0.005
a = torch.rand(20)
b = torch.histc(a, bins=20, min=0.0, max=1.0)
b /= b.sum()
plt.bar(np.linspace(0.025, 0.975, 20), b.numpy(), width=0.05)

torch.manual_seed()函数可以为CPU设置设定pytorch使用到的随机数种子, 可以控制各种分布(如均匀分布)以及数据集洗牌的顺序是一个固定的序列, 以使多次训练的过程中不发生较大的变化(到同一个epoch时会得到同样的模型). 输入是intlong.
如果是在GPU上运行模型, 则需要使用torch.cuda.manual_seed(args.seed) 函数来为当前GPU设置随机种子;如果使用多个GPU,应该使用torch.cuda.manual_seed_all()为所有的GPU设置种子。
Tensoris_cuda属性可以看到一个变量是否已被.cuda()
在main.py中设置好的种子可以影响到其调用的函数里面的数据抽取过程,如

torch.manual_seed(777) 
for j in range(4):
    for i, data in enumerate(train_loader_source):
        inputs, labels = data
        print(i, inputs.sum())
    print('')

跟把训练(这里只写了数据抽取)的代码放到另外一个py文件中再去调用:

from test import test
torch.manual_seed(777)       
test(train_loader_source)

会得到相同的输出。

torch自带了两个裁剪梯度的函数,分别限制梯度的范数和值:
torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm, norm_type=2.0) 对model中所有梯度非空的参数计算出一个总的梯度范数,并通过乘上一个系数,使得总的梯度范数不超过max_norm
torch.nn.utils.clip_grad_value_(model.parameters(), clip_value) 对model中每个梯度非空的参数单独做限制,使得单个参数的梯度被限制在[-clip_value, clip_value]之间
注:这里说的“参数”不一定只有一个值,而是诸如conv.weightfc.bias这样算是一个参数

(先看《Python命令及使用方法》一文中的numpy的seed部分讲解再来看torch会发现其实它们很像)torch.manual_seed(123)设置随机数种子,torch.get_rng_state()可以获得当前随机数生成器的状态,它是一个长度为5056的类型为uint8的tensor,下记为state。torch所使用的随机数生成算法也是MT随机数生成算法,只是它的state长得跟numpy和random不太像。由于元素类型是uint8,要想表示uint32,就需要用四个连续的uint8来表示,然后为了正确分割,每4个有数据的uint8后面会跟4个值为0的元素,可以理解成分隔符。数列的最后40个元素固定全为0,可以理解为结束标记。数列的前24个元素含义丰富一点。前4个值可能跟seed有关,然后跟着4个0,第9个数每生成一次随机数就减1(如果生成一个3x3的矩阵就减9),第9跟第10个数一起表示从624到1的变化,例如[1 0]表示1,[112, 2]表示624。后面跟着两个0,然后是[1 0 0 0],第17跟第18个数一起表示从1到624的变化,刚好跟前面反过来,每生成一次随机数就加1,前面减多少这里就加多少,二者的和始终为625。后面也跟着两个0,然后是[0 0 0 0]。到这里一共24个元素。设置seed时除掉前24跟后40,(5056-24-40)/8刚好就是624个数,这样就跟numpy的state对得上了。当从1增大到超过624之后,整个624数列会发生变化。每次torch.manual_seed()的时候,整个624数列会发生变化,state的前4个值被赋值为我们设置的seed,同时两个计数器分别被设置为[1 0]和[0 0],也即1和0,走一步之后变为624和1。计数器2只在初始化时为0,其余时刻均在[1, 624]内。state[:4]应该只参与了最开始设置seed时候的624数列计算,在后续的随机数生成过程中再也没有变化过,应该是不再发挥作用了。
总结一下,如果是想要控制某个位点的输出一致,使用torch.manual_seed()即可;如果是前面已经设置了seed了,现在想要核对中间的状态或者是从中间位置继续生成随机数,且想要保持一致性,那就要用如下的方法:

state = torch.get_rng_state()
torch.set_rng_state(state)

跟cuda相对应的有torch.cuda.manual_seed()torch.cuda.get_rng_state()torch.cuda.set_rng_state(),以及torch.cuda.manual_seed_all()torch.cuda.get_rng_state_all()torch.cuda.set_rng_state_all(),但是torch.cuda是没有生成随机数相关的函数的,因此这些函数的作用暂时不明确。


关于网络模型
建立网络

基本语句结构:

class Share_convs(nn.Module): # 首先建立一个继承自nn.Module的类
    def __init__(self):
        super(Share_convs, self).__init__() # 不要忘了初始化
        # 建立模型
    def forward(self, x):
        # 前向传播,获得输出
        return out

建立模型有多种方法:

# 一、简单粗暴式,独立写出每一个网络组件,建立模型的代码这么写:
x = self.conv1(x)
x = self.prelu(x)
x = self.conv2(x)
# 前向传播的代码这么写:
out = self.conv1(x)
out = self.prelu(out)
out = self.conv2(out)
out = self.prelu(out)
# 此时生成的网络组件名字就分别叫做conv1, prelu和conv2,训练中若需要调整对应的梯度值,可以通过名字
# 找到对应的参数。Sequential里有4个组件,名字是按计算流的顺序起的,如0,1,2,3。
# 二、Sequential式,一次性列出所有组件,建立模型这么写: 
self.model= nn.Sequential(
        nn.Conv2d(1,20,5),
        nn.PReLU(),
        nn.Conv2d(20,64,5),
        nn.PReLU()
        )        
# 前向传播只需一行语句:
out = self.model(x)
# 跟方法一相比,失去了外面的3个独立组件。目前尚不明确是按外面的独立组件名称来寻找还是按Sequential里面的名称来寻找
# 三、命名Sequential式,Sequential里的每个组件都可以有自己的名字,建立模型这么写:
# 需要导入OrderedDict模块
from collections import OrderedDict          
self.model = nn.Sequential(OrderedDict([
        ('conv1', nn.Conv2d(1,20,5)),
        ('prelu1', nn.PReLU()),
        ('conv2', nn.Conv2d(20,64,5)),
        ('prelu2', nn.PReLU())
        ]))     
# 前向传播只需一行语句:
out = self.model(x)
# 四、ModuleList辅助式,一次性建立多个相同结构,其主要作用是节省代码量,建立模型这么写: 
self.model = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
# 前向传播使用一个for循环:
# ModuleList can act as an iterable, or be indexed using ints
for i, l in enumerate(self.linears):
    x = self.linears[i // 2](x) + l(x)
# 可以通过print(model.parameters)语句来查看网络参数的情况(名称,位置等)

需要注意的是,如果OrderedDict里面有两个组件名字重复了,那么Sequential的建立结果很可能与我们的预期不同。若第二个conv组件改名为conv1,则在Sequential里,只会有一个卷积层,处于最开头的位置,且为nn.Conv2d(20,64,5),也即是说第三行的conv1设置对第一行的conv1设置做了一个刷新(覆盖),而不是我们所想的还会有两个卷积层。prelu也是一样的道理,重名之后就只剩一个激活层了。而在Sequential外面,由于两个PReLU组件结构完全相同且不可学习,pytorch将他们归为同一个组件,称为prelu;即使Sequential里面卷积层被覆盖了,外面仍然有conv1, conv2两个分别为nn.Conv2d(1,20,5)和nn.Conv2d(20,64,5)的卷积层。

进行实例化之后

在我们建立好一个网络并实例化之后: net = Net()
网络就有了一些元素, 比如说:
net.state_dict()
类型: Orderdict(有序字典)
作用: 保存模型的参数
比如说, 我们有一个两层的网络, 在Net类里我们定义的网络模型变量叫model, 那么state_dict()的内容就会是:
Key: ‘model.0.weight’ Value: 第一层网络的权重张量
Key: ‘model.0.bias’ Value: 第一层网络的偏置张量
Key: ‘model.1.weight’ Value: 第二层网络的权重张量

以此类推.
键在有序字典里的排列顺序是按首字母来排的, 不是按网络中的实际先后顺序排的, 这一点需要注意.
通过update方法可以使用已有的参数字典对模型进行更新:

model_dict = net.state_dict()
model_dict.update(pretrained_dict)
net.load_state_dict(model_dict)

注意如果pretrained_dict里存在model_dict没有的键值对, 它也会添加进model_dict里, 最后就会造成model_dict变大了不跟原来的网络匹配了, 在装载回原网络时就会出错, 所以应该先对pretrained_dict进行筛选:

model_dict = net.state_dict()
# 只保留pretrained_dict中与model_dict共有的部分
pretrained_dict_temp = {k: v for k, v in pretrained_dict.items() if k in model_dict}
model_dict.update(pretrained_dict_temp)
net.load_state_dict(model_dict)

如果是想把预训练的特征提取网络,用在自己的分类任务上,则一般需要修改最后的输出单元数:

import torchvision.models as models
model = models.resnet50(pretrained=True)
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, my_out_features)

另一种方法是调用model.children()方法,截掉部分网络(原文地址):

# 去掉最后两层
resnet_layer = nn.Sequential(*list(model.children())[:-2]) # 但是此法对于某些结构的网络(如Inception-v3)似乎不奏效,会把原来的网络连接顺序搞乱,导致维度异常或者结果错误
关于网络参数

记变量model为一个网络实例:model = Model(args)
当我们建立的网络中包含有权重共享的部分的时候,为了确保网络建立的正确性,可以查看一下网络里面的各个参数。两个网络各个组件的名字的是一样的,这点不能作为区分,但是它们共享的部分权重应该相同,不共享的部分在进行了随机初始化之后应该不同,为了方便查看可以直接输出参数张量的和而不是整个张量:

for name, param in G1.named_parameters():
    print(name, param.sum())
for name, param in G2.named_parameters():
    print(name, param.sum())

f = net.parameters()返回整个网络的参数,为torch.nn.parameter.Parameter类型;
f.data是torch.FloatTensor类型
torch在建立网络后会以默认方式初始化一遍,如果想应用其他初始化方法,可以在后面自己再进行修改。

关于网络组件(module)

model.modules()返回网络里面的所有组件。重复的组件(如两个一样尺寸的Linear)将只被返回一个。

一些其它的内容
model.apply

model.apply(fn)方法是个利器,它递归地将某个函数fn应用到网络的每一个组件上,例:

def init_weights(m): # 一个初始化权重的函数
    print(m)
    if type(m) == nn.Linear: # 是FC层就初始化权重为1
        m.weight.data.fill_(1.0)
        print(m.weight)

net = nn.Sequential(nn.Linear(3, 3), nn.Linear(2, 2))
net.apply(init_weights)

网络的结构长这样:

net(Sequential)
│   nn.Linear(3, 3)
│   nn.Linear(2, 2)

apply的输出结果为(注释内容是为了方便理解,不是程序输出的内容):

Linear(in_features=3, out_features=3, bias=True) # init_weights函数往下一钻,先遇到了最底下的nn.Linear(3, 3)
Parameter containing:
tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], requires_grad=True)
Linear(in_features=2, out_features=2, bias=True) # 接着来到nn.Linear(2, 2),应该是深度优先搜索
Parameter containing:
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)
# 最后去访问上层的Sequential,它不是Linear,故不做初始化也不输出weight
Sequential(
  (0): Linear(in_features=3, out_features=3, bias=True)
  (1): Linear(in_features=2, out_features=2, bias=True)
)

Pytorch 模型的网络结构可视化

关于hook

pytorch中获取模型input/output shape
挂钩(直译)大法,挂在那里,每次有数据来就调用一次。其寓意也许是人来回走动的时候,碰到了一下“挂钩”就会启动连锁反应。
下面称组件为module,组件视情况不同,可能为一个conv, fc层,也可能是一个Sequential,还可能是一整个model。
module.register_forward_hook(hook)
给该组件上挂钩,每次前向传播完(也即执行完module.forward())之后会调用一次hook函数,hook的参数表必须是hook(module, input, output),且返回值必须是None(python里面不写return语句默认就是返回None)
input是组件的输入,是一个tuple,如果网络只有一个输入,那就是一个只有1个元素的tuple,保存着组件的输入张量;output是组件的输出,当网络只有一个输出时是个Tensor,有多个输出的情况尚未知晓,待补充
经人肉实验,得到了一些实验结果:

forward_hook的遍历顺序:先深搜到达最底层最前面的组件,然后如果它位于一个Sequential中,则遍历完Sequential里面的组件,之后访问Sequential,之后访问Sequential的父亲,再下去访问其父亲下面的底层组件,以此类推。(Sequential有不止一个兄弟的情况尚未实验过,不知道其父亲是否会被多次访问,或者是否会在访问完其所有兄弟之后再去访问父亲,还是先访问父亲再去访问各个兄弟,访问兄弟时不再访问父亲。待补充)
backward_hook的遍历顺序:
backward_hook中的hook函数的参数表必须是hook(module, grad_input, grad_output),这里解释一下后面两项:grad_input保存的是计算梯度所需的张量,对于一个Conv2d组件来讲,grad_input是一个3元素tuple,其中grad_input[0].size=output.size()(这里的output是指forward_hook的hook函数里面的output,也即该组件的输出张量),包含后一个组件传过来的梯度;grad_input[1].size=weight.size(),包含的是(有点问题,尚未完成
module还可以挂register_forward_pre_hook(在前向传播之前)、register_backward_hook(在反向传播之后)。
用于module的反向hook详解
参数表:hook(module, grad_input, grad_output)

  • module指的是即将被挂上挂钩的组件。这里的"input"和"output"是相对于前向传播而言的,grad_input不是指梯度的输入,而是指输入张量的梯度。同理,grad_output是指输出张量的梯度,它来自更靠近Loss的上一个组件。
  • 对于Conv2d组件,输入有三种,图像,卷积核和偏置,输出只有一种就是图像,所以它的grad_input是3个元素,而grad_output是1个元素。grad_output从Loss的方向传回来,与组件的参数(权重,偏置等)做计算,就得到了grad_inputgrad_output[0]用于运算之后就被丢弃了,grad_input[1], grad_input[2]是对权重和偏置的梯度,仅在本组件中起作用,grad_input[0]是对输入图像的梯度,它将继续往前传,也即如果修改了grad_input[0],前面所有组件的梯度都会受到影响,而修改grad_input[1], grad_input[2]grad_output[0]则不会。
  • 对于Linear组件,对应关系稍有不同。由公式 h = W X + b h=WX+b h=WX+b知,Linear组件的输入也是有三种,图像,权重和偏置,输出一种图像。它的grad_input[0]是个 b a t c h _ s i z e × C o u t batch\_size×C_{out} batch_size×Cout矩阵,逐行求和即得到每个偏置的梯度;grad_input[1]是准备传给下一层的图像的梯度,grad_input[2]是权重的梯度。
    Tensor.register_hook(hook)
    注册一个后向的挂钩,在组件对应的梯度被计算出来(也即执行完module.backward())之后,调用一次hook函数,hook的参数表必须是hook(grad),可以返回Tensor或者None。示例:
v = torch.tensor([0., 0., 0.], requires_grad=True)
h = v.register_hook(lambda grad: grad * 2)  # double the gradient
v.backward(torch.tensor([1., 2., 3.]))

backward时直接使用一个张量为v.grad赋值,赋值完成后hook启动,将新增加的梯度翻倍(不是g,而是 δ g \delta_g δg,因此v.grad最后为[2,4,6]。
挂钩可以叠加,如果重复执行第二个语句,以后翻倍就是翻4倍,再执行一次就翻8倍。
使用h.remove()语句来移除这个挂钩。如果叠加了多次,那就要多remove几次,remove一次去掉一层。


torch自己下载的预训练网络参数文件一般会保存在家目录的.torch文件夹下
把模型和数据搬上显卡: https://zhuanlan.zhihu.com/p/31936740
python xx.py 前加上一个CUDA_VISIBLE_DEVICES=1, 可以限制模型只在某张显卡上运行,加上CUDA_VISIBLE_DEVICES=""则是不使用GPU。如果在run.sh里面指定了GPU,而内层在os.system(command)时不指定,那么内层也会默认只使用外层指定的GPU;如果想要内层能使用多个GPU,则在内层指令里面再次添加CUDA_VISIBLE_DEVICES=x,y,z即可
requires_grad 与 detach 区别&梯度传递细节

并行计算

pytorch使用多gpu进行运算只需一行代码:model = nn.DataParallel(Model(args))
指定某几个GPU, 可将CUDA_VISIBLE_DEVICES=0修改为CUDA_VISIBLE_DEVICES=0,2,3,或者在代码中设置os.environ["CUDA_VISIBLE_DEVICES"] = '12,13'
同时,DataParallel也支持指定单卡,可以在单卡上跑而不会报错
Pytorch 多 GPU 并行处理机制
DataParallel 的行为&均衡负载
DataParallel包装的模型在保存时,权值参数前面会带有module字符,因此多GPU训练的模型读到单gpu上时,读取代码需要做点小改动:

# 单GPU训练读取模型
checkpoint = torch.load(save_path)
model.load_state_dict(checkpoint['model_state_dict'])
# 多GPU训练的模型读到单gpu上(多GPU上的模型 直接把网络键名里的'module.'去掉)
state_dict = torch.load(save_path)
from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict.items():
    namekey = k[7:] # remove `module.`
    new_state_dict[namekey] = v
model.load_state_dict(new_state_dict)

在cpu/gpu上加载预先在gpu/cpu上训练好的模型

# 把在GPU上训练好的模型加载到CPU上
model = torch.load(model_loadpath, map_location=torch.device('cpu'))

现在基本都是用DistributedDataParallel了:PyTorch分布式训练简明教程
进行DistributedDataParallel时有个broadcast_buffers参数,用于控制buffers是否要在每一张卡上同步,还是各用各的,参考链接
PyTorch 多进程分布式训练实战
分布式训练中几个名词的含义(参考链接):
node,结点,指一台物理机器,比如一台服务器,不同的服务器有不同的node_rank也即机器序号;
rank,等级,指一个进程,如果一个进程跑一张卡的话,那就是n张卡有n个rank值,从0到n-1递增,其中n=0的进程是主进程。
local_rank,本地等级,指在某一台物理机器上的进程序号,如果这台机器有x张卡,那么local_rank就是从0到x-1。不同的node上的进程有各自的local_rank。

torchvision库

torchvision包的三个用途:

  1. 提供流行的model,同时可以针对常用数据集直接进行处理。
  2. 针对torch.utils.data.Dataset进行了扩充(见下文),torchvision.datasets.ImageFolder是torch.utils.data.Dataset的子类, 都返回一个迭代器。
  3. 提供现成的torchvision.transforms ,对图片进行预处理, 从而避免自己写的麻烦。

torchvision.transforms
文档地址

transforms.Resize(400) # Resize函数将图像的短边放缩到400像素
transforms.Resize((400,300)) # 将图像放缩成高400宽300像素
transforms.RandomCrop(200) # 中心点随机的裁剪,切出200x200的小块(HxW)
transforms.RandomCrop((300,200)) # 切出300x200的小块

对torch.utils.data.Dataset和torch.utils.data.DataLoader的介绍(好文)
有趣的是torch.utils.data.DataLoader在刚导入torch包时是不存在的, 而在import torchvision语句之后, 便多出了一些属性, 包括这个DataLoader. 所以如果没有导入torchvision包, 是没有办法使用DataLoader的, 需要注意一下.
常见的两种数据集形式:

  • 所有图片都在同一个文件夹内。(这个用torch.utils.data.DataSet类就行,见下文)
  • 每个类的图片放在单独一个文件夹, 常见于分类任务,比如imagenet数据集有1000类,对应1000个文件夹。(用torchvision.datasets.ImageFolder(‘image_dir_root’ ))

第二类数据集的目录结构如下:

root/ants/xxx.png
root/ants/xxy.jpeg
root/ants/xxz.png
.
.
.
root/bees/123.jpg
root/bees/nsdf3.png
root/bees/asd932_.png

上面关于torchivision库的介绍转自:https://blog.csdn.net/Hungryof/article/details/76649006
torchvision数据集(如torchvision.datasets.ImageFoldertorchvision.datasets.CIFAR10等)的输出是在[0, 1]范围内的PILImage图片。
torch.utils.data.Dataset是一个抽象类, 自定义的Dataset需要继承它并且实现三个成员方法.
SR问题的label是一张图片,这时只能靠自定义Dataset了:https://www.cnblogs.com/denny402/p/7512516.html
自定义Dataloader读取数据(待补充)
pytorch中的default loader代码如下:

def default_loader(path):
    from torchvision import get_image_backend
    if get_image_backend() == 'accimage':
        return accimage_loader(path)
    else:
        return pil_loader(path)
	

通常我们会对dataloader进行枚举:for i, data in enumerate(dataloader):
data里面就自动包含了数据和对应的标签了(我猜是因为我们告诉ImageFolder每一个文件夹是一个类, 所以在loader的时候它就知道要按图片是从哪个子文件夹来的来给出标签), 而不是说输入数据要一个dataloader, 标签要一个dataloader.
torch.utils.data.DataLoader()中的shuffle=True是指, 在每一个epoch开始时, dataloader会进行一次洗牌, 使得每个epoch里面图片的排列顺序都是不一样的; 而shuffle=False就是在第一个epoch开始时进行一次洗牌, 后面就一直按着这排列顺序来训练. 不管是True还是False, 都会有一次洗牌, 不会说按照文件路径的顺序直接来. 另外一个可能有用的参数是drop_last, 默认是False,即保留最后一个批,这个批的样本数通常不足batch_size,如果设为True则直接丢掉这个批。(我之前是自己写的这个操作,后来看了别人的代码才发现DataLoader自带了这个操作)
dataloader貌似只能返回Tensorndarrayint float的向量,listdict都没有办法正常地传出来,出来的长度总是1。所以如果真的需要传listdict,要考虑先转成ndarray。如果是ndarray,出来的时候pytorch会自动把它转成Tensor,这点需要注意一下
dataloader的num_worker参数控制的是准备数据的线程数,从实验的输出来看,pytorch貌似是有个缓冲区,会放置4*num_workers的图片等待被读取,而不是每次要数据的时候才开始运行多个线程读取数据。
顺带一提,在跑模型的时候去ps -aux | grep username,将会看到有不止一个COMMAND值为我们写的python xx.py语句的进程,其中一个TIME值很高,其他的都不高且接近。那些不高且接近的进程其实就是dataloader里面分出来的负责读取数据的workers,我们设置的num_workers是多少,就会看到多少个这种进程。因为它们只在读取数据时才工作,所以总的运行时间并不长,TIME值最高的就是主代码了,基本上一直都在运行,TIME值跟真实世界流逝的时间可能比较接近。
dataloader 使用batch和 num_workers参数的原理是什么?

在运行模型之前

查看NVIDIA 显卡 nvidia-smi 查看CUDA版本nvcc -V
选择训练时使用的GPU:
import os os.environ["CUDA_VISIBLE_DEVICES"] = "N" (N=0,1,2,...)
Pytorch的包名是torch,导入时应import torch而不是pytorch
在一个语句前面加上CUDA_VISIBLE_DEVICES=N(N=0,1,2,…),可以限制这个语句后面的命令在第N个GPU上运行,不会乱跑到其他GPU上干扰别人工作
有时候训练到一半, 命令行窗口突然没有了动静, 很长一段时间内没有输出新东西, 光标也不会闪, 这个时候可能是程序卡死了, 按下Ctrl+C或许能够起死回生让它继续运行, 否则就只能重新来过了.
自动查找并使用空闲GPU(原文地址):

# 查看GPU memory,并将结果保存在tmp中
os.system('nvidia-smi -q -d Memory |grep -A4 GPU|grep Free >tmp')
# 读取gpu memory
memory_gpu=[int(x.split()[2]) for x in open('tmp','r').readlines()]
# 求剩余memory最多的显卡号,并设置CUDA_VISIBLE_DEVICES为该显卡
os.environ['CUDA_VISIBLE_DEVICES']=str(np.argmax(memory_gpu))
os.system('rm tmp')

后来发现可以通过管道直接读取shell命令的运行结果(原文地址):

from subprocess import Popen, PIPE
cmd = ["nvidia-smi",
       "--query-gpu=index,utilization.gpu,memory.total,memory.used",
       "--format=csv,noheader,nounits"]
p = Popen(cmd, stdout=PIPE)
output = p.stdout.read().decode('UTF-8')
Tensorboard的使用

Tensorboard 是一个动态可视化数值的工具,同时也能可视化静态的神经网络结构。
Tensorboard 包含两部分功能:

  • 将网络结构、动态数值以 protocol buffer 格式写到文件里。
  • 读取网络结构、读取动态数值,并展示在浏览器中。

第一部分功能,以python包形式存在。编程者 import tensorboard 从而使用API将动态的数值以protocol buffer格式,不断地写入文件。
第二部分功能,以可执行程序形式存在。这个程序在win下叫 tensorboard.exe,linux下叫 tensorboard。该程序是一个web服务器,能够不停地读取本地文件,查询是否有新数值要展示,再应答给网页。(转自:https://blog.csdn.net/u010469993/article/details/80950510 )
安装:

pip install tensorboard
pip install tensorboardX

第一步 创建文件写控制器, 将数值序列写入文件
第二步 在命令行里使用tensorboard读取protocol buffer 格式的数值:
tensorboard --logdir logs --port 8888
分别使用一个终端来完成.

具体的操作方法:

from tensorboardX import SummaryWriter
# 首先创建一个文件写控制器
writer = SummaryWriter(log_dir='logs')
# 注: 下面的代码只是一些零散的示例, 不是一段完整的能运行的代码
# 在写入变量之前应先计算出它的值, 然后再add
# 写入一个标量(注意, 该操作不是一次性的, 而是在每次迭代里面都要写一次, 相当于一次加一笔上去)
writer.add_scalar('data/x', x, epoch) # 展示为一个图里一条线
# 写入一组标量, 展示为一个图里多条线
writer.add_scalars('data/scalar_group', {'x': x,
                                         'y': y,
                                         'loss': loss}, epoch)
# 插入一个直方图, 在HISTOGRAM菜单中显示
writer.add_histogram('zz/x', x, epoch)
# 插入提示文本, 在TEXT菜单中显示
writer.add_text('zz/text', 'zz: this is epoch' + str(epoch), epoch)
# 把标量写入到JSON文件中, 以供外部处理
writer.export_scalars_to_json("./test.json")
# 最后记得关闭控制器
writer.close()

tensorboard 出现locale.Error: unsupported locale setting错误:在~/.bashrc中写入export LC_ALL="en_US.UTF-8"原文链接

自定义对变量的操作(自定义层)

torch.autograd里的Function模块可以用来自定义对变量Variable的操作, 也可以用来自定义一个层置于模型中.
导入: from torch.autograd import Function as Function
使用:

class layer(Function):
    @staticmethod
    def forward(ctx, x, alpha):
        ctx.alpha = alpha
        return x.view_as(x)
    @staticmethod
    def backward(ctx, grad_output):
        output = grad_output.neg() * ctx.alpha
        return output, None        

注意到静态方法不需要实例, 因此函数的参数表里没有self这个参数. 调用该层时不是直接layer.forward(), 而是通过一个从Function类继承下来的apply函数, 该函数会自动执行forward和backward的操作. 要注意的是它还会传入自身作为一个参数, 就像是普通类一样. 而forward本不需要这个参数, 为了保持跟普通类的格式相同, 于是又定了一个参数, 叫ctx, 可能是context的意思(ctx不是关键字). 实际调用时只需传入两个参数, 即layer.apply(x, alpha)

性能优化

混合精度计算,显存减少30%左右,时间减少20%左右,acc降低0.5个点左右(Office31+ResNet50)

模型可视化

Netron是一款支持巨多格式的模型的可视化工具,只需pip install netron下载,然后
netron yolov5s.onnx --port 可用端口 --host 服务器ip,就可以在主机上像使用jupyter一样看到模型的可视化结果。

标注工具

labelme工具可以方便地标注用于分割任务的图像;
labelImg工具可以方便地标注用于检测任务的图像,简易教程在这

在只有图片文件的时候,需要在labelImg安装目录下的data/predefined_classes.txt写入所需的类别列表,每一行一个类别。当然也可以不使用这个文件,用自定义的文件:python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE],但这样必须顺带把要标注的图片目录也写上,比较麻烦。
在保存标注文件时,labelImg会将新的标签列表写进图片目录的classes.txt中,图片A.jpg的标签会保存为A.txt。在后续更改标注时,必须在图片目录下有classes.txt,因为labelImg要根据它来读取标注txt文件的标签信息并显示。但classes.txt并不会改变重新标注时labelImg提供给用户的标签列表,这块依然由data/predefined_classes.txt控制。如果有data/predefined_classes.txt但画好bbox后弹出的标注框里依然一个label name都没有,那就说明有问题,有可能需要用上述自定义标签文件语句来打开labelImg。

一些变更
Variable的.creator属性用于查找变量在计算图中的父节点,在2017年5月被修改为.grad_fn
在0.4.0及以后的版本中,已不需要Variable函数, 如果一个tensor需要求导, 直接设置.requires_grad=True即可.

环境配置

CUDA的正确安装/升级/重装/使用方式,好文

生僻bug记录
  1. 稍微改动了原先的代码然后用到一个新的数据集上,结果在一个完整的epoch结束前报错:OSError: Unrecognized data stream contents when reading image file
    解决方案:在确认代码没有可能导致报错的改动之后,感觉应该是数据集的问题。将数据集部分图片移除,只保留两个类,训练正常进行。逐步增加类数目直至报错。在不断缩小范围之后,最后终于抓出了导致报错的那一张图片,拉下来打开之后发现该图片被污染了,可能是下载过程中受损或者因其他原因受损。重新下载并更换该图片,报错解除。
  2. 网络能跑能更新参数,使用自己写的check_grad函数时却会报错:AttributeError: ‘NoneType’ object has no attribute ‘abs’
    可能问题:可能是forward的部分做过修改,比如把最后的fc去掉了,但是在init部分没有注释掉self.fc的定义,所以这个fc虽然没有启用,但一直存在,且它的grad是None,检查一下是不是这个问题,如果是的话注释掉没有使用的网络层的定义即可。已经因为这个问题出过两次类似的bug了。
  3. RuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you must use the ‘spawn’ start method
    原因:dataloader里面不能出现.cuda(),只能一直在cpu上运行。这是CUDA的基本局限,只能避开,目前无法解决。
    解决方案:把num_workers设成1,或者去掉.cuda()
  4. RuntimeError: CUDA error: initialization error
    多发生在load data期间,也可见于torch.autograd.Function的forward阶段
    原因:在多进程的代码之前就调用了.cuda(),
    解决方案:先读取数据,再.cuda();不要往dataloader或者torch.autograd.Function的forward函数中传is_cuda属性为True的tensor
  5. RuntimeError: cannot pin ‘torch.cuda.FloatTensor’ only dense CPU tensors can be pinned
    原因:是pin_memory的问题
    解决方案:初始化dataloader的时候设置pin_memory=False,不过num_workers还是要设成0或者1
  6. 用一个在gpu上的张量而非torch定义的网络参数作为optimizer的参数时,报错 “ValueError: can’t optimize a non-leaf Tensor”
    原因:原始张量跟.cuda()后的张量不是同一个变量,.requires_grad=True的操作只对当前变量有效,所以先改成True再cuda()就报错了,改成先.cuda()再True就可以了
    PS: 若a.requires_grad=True, b=a.cuda(), 则backward后a是能正常有梯度的
  7. OMP: Warning #190: Forking a process while a parallel region is active is potentially unsafe.
    可能问题:同时使用多个dataloader时,有时候会出现这个警告,同时cpu占用率飙升。
    解决方案:将DataLoader初始化参数中的pin_memory设为False,问题解决。参考链接
  8. RuntimeError: Caught RuntimeError in replica 2 on device 2.
    可能问题:数据目录没设对,导致读不到图片,导致没有label,所以info没有下标不可迭代(常见于搬来人家的github,在自己的数据集上跑一开始没设对目录)
    解决方案:把数据目录改对
    参考链接
  9. TypeError: tensor is not a torch image.
    多发:load data的时候
    原因:漏了transforms.ToTensor(),或者是ToTensor()操作没有在transforms.Normalize之前
    解决办法:见原因。参考链接
  10. RuntimeError: stack(): functions with out=… arguments don’t support automatic differentiation, but one of the arguments requires grad.
    原因:在dataset类的__getitem__里面把变量的requires_grad设为True,stack(), cat()等函数不允许把多个需要梯度的变量这样堆叠到一起
    解决方案:先读取整个batch的数据,然后再把整个batch组成的tensor的requires_grad设为True
  11. 对tensor进行扩充之后,发现赋值的结果出现异常
    原因:扩充的时候使用了expand方法,它相当于浅拷贝出了很多个数据,改一个就会牵连到其他
    解决方案:改用tensor.repeat,这是深拷贝,各个元素之间不会互相影响
    另外,expand还可能导致loss.backward的时候报错对同一个变量进行了多次backward,并提示应使用retain_graph=True
  12. 关于pytorch的cpu高占用问题:参考链接1 参考链接2 参考链接3
    在这里插入图片描述
  13. Unable to determine the device handle for GPU 0000:3E:00.0: GPU is lost. Reboot the system to recover this GPU
    原因:gpu过热,进一步分析,3卡的风扇在显卡高温时依然没有加大负荷,需要手动修改风扇的温度-转速曲线,让它在显卡温度较高时转速更大,以保证温度不超过85度。设置方法
    如果nvidia-settings报错了不可用,则需要手动安装一个:首先nvidia-smi看第一行的版本号,形如440.95.01,然后在这里找到对应的deb文件链接(如果是Ubuntu16.04就对应改一下url),然后在服务器上wget下来,再sudo dpkg -i xx.deb,就可以正常使用nvidia-settings了。
    cool_gpu文件中的第一行dir跟第二行nvidia-smi命令的位置要根据自己的实际情况做修改。
  • 25
    点赞
  • 248
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值