附录:PyTorch记事本

tensor.cuda()
在使用GPU的情况下,一般会将所有相关tensor都放到GPU上计算,所以如果仅仅model=model.cuda(),程序将不能正确执行,因为输入tensor和输出tensor还没布置到GPU上,还需要:

x=x.cuda()
y=y.cuda()
model=model.cuda()

注意,这样的迁移比较特殊,在完成设备迁移的同时,叶子张量属性is_leaf并不会发生变化,虽然x=x.cuda()不是in-place,但叶子节点并没有变成非叶子节点,这对训练有重要作用
torch.nn.CrossEntropyLoss
对于一般的分类训练,会写:

loss_fn=torch.nn.CrossEntropyLoss()
...
loss=loss_fn(y_pred,target)

注意到:
y_pred.shape:(batch_size,class_number);
target.shape:(batch_size);
所以target的每个元素需要是整数(一般是int64),取值范围为[0,class_number-1],这才能被函数认为是真实类别
注意一个细节:当使用了torch.nn.CrossEntropyLoss的时候,其实不需要在model最后一层添加torch.nn.Softmax(),因为在官方文档中,pytorch使用CrossEntropyLoss计算交叉熵的公式为:

fig1

这也正好解释了前面target值在[0,class_number-1]的奇怪规则
torch.no_grad()
torch.no_grad()的作用是使在with关键字下的计算图不被追踪,此时,就算叶子张量的requires_grad=True,也可以进行in-place操作:

x = torch.tensor([1],dtype=torch.float,requires_grad=True)
with torch.no_grad():
    x-=0.1

另一方面,计算图不被追踪的言外之意是Disabling gradient calculation(禁用梯度计算),相当于在torch.no_grad()下,显存不必将资源用于梯度计算,节省了显存,适用于inference;


model.eval()与torch.no_grad():model.eval()不开启BN和Dropout,注意只有torch.no_grad()可以关闭梯度计算,用于节省显存。为了确保梯度传播的准确,在loss.backward()前执行optimizer.zero_grad()即可。


tensor.max()
常用于索引分类结果:

tensor.max(dim=None)->(Tensor,Tensor)

dim用于选择对哪个轴方向进行操作,返回对象元组有两个元素,一个是索引到的最大值结果,一个是最大值所在轴上的位置;
假设现有前向传播的结果pred(N,4),N个样本,4个类别,若写pred.max(dim=1)代表沿着第二轴(列轴即水平方向)求最大值
torch.bmm
批处理张量乘法bmm:batch matrix multiplication,在batch中,两组张量对应相乘:

(b,m,n)*(b,n,p)->(b,m,p)

tensor.unsqueeze和tensor.squeeze
tensor.unsqueeze用于增加维度,增加在哪一维度由关键字参数dim确定:

x=torch.randn(5,3)
x.unsqueeze(dim=2).size()
#>torch.Size([5, 3, 1])

x=torch.randn(5,3)
x.unsqueeze(dim=1).size()
#>torch.Size([5, 1, 3])

对应的,tensor.squeeze用于去除冗余维度(某个维度shape为1):

x=torch.randn(5,3,1)
x.squeeze(dim=2).size()
#>torch.Size([5, 3])

x=torch.randn(5,3,1)
x.squeeze(dim=0).size()
#>torch.Size([5, 3, 1])

注意squeeze和unsqueeze不是in-place的
反向传播公式的简单解释
在pytorch入门中,简要给出了反向传播计算权重梯度的公式:
∂ J ∂ W = ∂ J ∂ z a \frac{\partial J}{\partial W}=\frac{\partial J}{\partial z}a WJ=zJa
为什么要乘z前面的输出a?其实上式可以理解为:
∂ J ∂ W = ∂ J ∂ z ∂ z ∂ W \frac{\partial J}{\partial W}=\frac{\partial J}{\partial z}\frac{\partial z}{\partial W} WJ=zJWz
因为下一个激活前的输入值z为: z = W T a z=W^Ta z=WTa
torch.randint
用于生成随机的整数张量(dtype=torch.int64):

randint(low=0, high, size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False) -> Tensor

值在low到high之间,形状由size决定:

torch.randint(6,(2,2))
"""
tensor([[5, 1],
        [4, 3]])
"""

tensor.fill_
属于in-place操作,将张量内部的值进行替换,内存地址不变,fill_只支持0维张量(元素是一个数值),fill_不改变数据类型:

x=torch.tensor(1)
y=torch.tensor(2.)
x.fill_(y)
print(x)
x.dtype

#tensor(2)
#torch.int64

torch.multinomial
常用于随机采样:

multinomial(input, num_samples, replacement=False, out=None) -> LongTensor

input通常是概率序列(其实只要是浮点型的一维张量就行),返回为LongTensor,内容是采样序列的索引,值越大越容易被采样到;
replacement代表有放回,设为True可以无限次采样:

x=torch.tensor([8.,9.,11.,23.,100.])
torch.multinomial(x,10,replacement=True)
#tensor([4, 0, 2, 3, 4, 3, 4, 4, 4, 0])

tensor.transpose和tensor.permute
transpose只能选择两个维度进行调整,permute可以任意设置维度的顺序:

transpose(dim0, dim1) -> Tensor
permute(*dims) -> Tensor

实例如下:

x=torch.randn(5,3,2)
x.transpose(1,2).size()

#torch.Size([5, 2, 3])

x=torch.randn(5,3,2)
x.permute(2,1,0).size()

#torch.Size([2, 3, 5])

torch.nn.BCEWithLogitsLoss
这个损失函数只能用于二分类问题,该损失函数包括两个部分:一个sigmoid函数和BinaryCrossEntropy函数;
使用举例:

crit = nn.BCEWithLogitsLoss()
# preds [batch_size]
# batch.label [batch_size]
loss = crit(preds, batch.label)

可见网络输出与标签的shape都是[batch_size],假设对于batch中的一组数,网络输出 x i x_{i} xi,标签为 y i y_{i} yi,则loss计算为:
l o s s = − [ y i l o g σ ( x i ) + ( 1 − y i ) l o g ( 1 − σ ( x i ) ) ] loss=-[y_{i}log \sigma (x_{i})+(1-y_{i})log(1- \sigma (x_{i}))] loss=[yilogσ(xi)+(1yi)log(1σ(xi))]
损失函数两个项反应了属于第0类的对象被分为第0类,属于第1类的对象被分到第1类,才能最小化loss
torch.round
对结果四舍五入:

x=torch.randn(5,3)
print(x)
y=torch.round(x)
print(y)
"""
tensor([[ 1.3226, -0.6480, -1.0701],
        [-1.4932, -0.6328,  1.0331],
        [ 0.9444,  0.4172,  0.8942],
        [-0.3619,  0.1024, -1.8857],
        [-3.2601,  1.2551, -0.5345]])
tensor([[ 1., -1., -1.],
        [-1., -1.,  1.],
        [ 1.,  0.,  1.],
        [-0.,  0., -2.],
        [-3.,  1., -1.]])
"""

张量拼接torch.cat
张量可以通过cat拼接:

torch.cat(tensors, dim=0, *, out=None) → Tensor

dim决定了拼接操作基于的轴,比如:

hidden=torch.randn(4,2,3)#[4,batch_size,hidden_size]
print(hidden.size())
hidden=torch.cat((hidden[-1],hidden[-2]),dim=1)
print(hidden.size())

#torch.Size([4, 2, 3])
#torch.Size([2, 6])

x=torch.randn(2,3)
y=torch.randn(2,6)
torch.cat((x,y),dim=1).size()
#torch.Size([2, 9])

torch.nn.LogSoftmax和torch.nn.NLLLoss
torch.nn.LogSoftmax的本质就是对每个元素都计算LogSoftmax:
L o g S o f t m a x ( x i ) = l o g ( e x p ( x i ) ∑ j e x p ( x j ) ) LogSoftmax(x_{i})=log(\frac{exp(x_{i})}{\sum_{j}exp(x_{j})}) LogSoftmax(xi)=log(jexp(xj)exp(xi))
输入输出张量的shape不会改变:

x=torch.randn(6,3)
print(x)
# 沿着列轴方向操作
F.log_softmax(x,dim=1)
"""
tensor([[ 0.5185, -0.4387,  0.8417],
        [ 1.0252,  0.8140, -0.1921],
        [ 0.6522,  0.2619, -1.5904],
        [-0.1942, -0.5435,  0.5055],
        [-1.3790,  0.2573,  0.5489],
        [ 1.4560,  1.9608,  0.5123]])
        
tensor([[-1.0172, -1.9744, -0.6940],
        [-0.7446, -0.9558, -1.9620],
        [-0.5783, -0.9686, -2.8209],
        [-1.3133, -1.6626, -0.6136],
        [-2.5658, -0.9295, -0.6379],
        [-1.1137, -0.6090, -2.0575]])
"""

torch.log(torch.exp(x[0][1])/(torch.exp(x[0][0])+torch.exp(x[0][1])+torch.exp(x[0][2])))
"""
tensor(-1.9744)
"""

一般与logsoftmax配合使用的损失函数为NLL loss(负对数似然损失),因为logsoftmax取相反数正好就是交叉熵;
把交叉熵拆分开的原因是为了给类别加上权重信息,这样可以通过训练加强某些类别的分辨能力;常用于数据量不平衡的训练集;

torch.nn.NLLLoss(weight: Optional[torch.Tensor] = None, reduction: str = 'mean')

可选参数weight是一个向量,如果数据集共C个类别,则weight的元素个数为C,它反映了每个类别的重要程度;
假设现在调用一次NLLLoss:

"""
input (N,C)
traget (N)
output 一个值,具体值取决于reduction是sum还是mean,默认mean
如果reduction=None,则返回tensor (N)
"""

functional.nll_loss(pred,target)

pred应该是来自LogSoftmax计算后的张量,假设这个batch的tensor形状为 [ N , C ] [N,C] [N,C],则target应该是 [ N ] [N] [N],类似CrossEntropyLoss,target就像一个索引,其中每个元素值在0到 C − 1 C-1 C1之间;
负对数似然损失NLL loss的计算为:
1.reduction=None
l ( p r e d , t a r g e t ) = { l 1 , . . . , l N } l(pred,target)=\left \{ l_{1},...,l_{N} \right \} l(pred,target)={l1,...,lN}
l i = − ( w e i g h t [ t a r g e t [ i ] ] ) × ( x [ i ] [ y [ i ] ] ) l_{i}=-(weight[target[i]])\times (x[i][y[i]]) li=(weight[target[i]])×(x[i][y[i]])
2.reduction=sum
l ( p r e d , t a r g e t ) = ∑ i = 1 N l i l(pred,target)=\sum_{i=1}^{N}l_{i} l(pred,target)=i=1Nli
3.reduction=mean
l ( p r e d , t a r g e t ) = 1 ∑ j = 1 N w e i g h t [ t a r g e t [ j ] ] ∑ i = 1 N l i l(pred,target)=\frac{1}{\sum_{j=1}^{N}weight[target[j]]}\sum_{i=1}^{N}l_{i} l(pred,target)=j=1Nweight[target[j]]1i=1Nli

tensor.data_ptr()
在pytorch中,想要获取张量对象的地址,一般不使用id(),而是使用tensor.data_ptr()
tensor.data_ptr()可以返回tensor第一个元素所在的物理地址:

data_ptr()int

tensor.clone和tensor.detach
1.tensor.clone()
返回tensor的拷贝,返回的新tensor和原来的tensor具有同样的大小和数据类型;
但是,clone()返回的tensor是非叶子节点(有计算图连接);即:如果原tensor.requires_grad=True,则新tensor的梯度会流向原tensor,即新tensor的梯度会叠加在原tensor上:

>>> import torch
>>> a = torch.tensor(1.0, requires_grad=True)
>>> b = a.clone()

>>> a.data_ptr(), b.data_ptr() # 不指向同一物理地址
(94724519502912, 94724519533952)

>>> a.requires_grad, b.requires_grad  # 但b的requires_grad属性和a的一样,同样是True
(True, True)
>>> c = a * 2
>>> c.backward()
>>> a.grad
tensor(2.)
>>> d = b * 3
>>> d.backward()
>>> b.grad  # b的梯度值为None,因为是非叶子节点,梯度值不会被保存
>>> a.grad  # b的梯度叠加在a上
tensor(5.)

2.tensor.detach()
返回一个新的tensor,新的tensor和原来的tensor共享内存,但不涉及梯度计算,即requires_grad=False,相当于从计算图中分离出来了;
因为是共享同一物理地址,修改其中一个tensor的值,另一个也会改变,但如果对其中一个tensor执行内置操作,则会报错,例如resize_、resize_as_、set_、transpose_:

>>> import torch
>>> a = torch.rand((3, 4), requires_grad=True)
>>> b = a.detach()

>>> a.data_ptr(), b.data_ptr()  # 指向同一物理地址
(94724518609856, 94724518609856)

>>> a.requires_grad, b.requires_grad  # b的requires_grad为False
(True, False)

>>> b[0][0] = 1
>>> a[0][0]  # 修改b的值,a的值也会改变
tensor(1., grad_fn=<SelectBackward>)

>>> b.resize_((4, 3))  # 报错

关于梯度在计算图中的流动
属于同一个计算图的张量,其梯度在反向传播中累乘,对于两个不同的计算图,即计算图出现了分支,对两个分支分别计算梯度,两计算图共享张量的梯度需要求和:

import torch

x=torch.tensor(1.0, requires_grad=True)
y=2*x

a=torch.tensor(3.0, requires_grad=True)
b=3*a

# 两个计算图,共同依赖张量x
z=4*y+b
m=3*x

z.backward()
m.backward()

x.grad # tensor(11.) 11=4*2+3

torch.nn.Dropout
Dropout定义为:

torch.nn.Dropout(p: float = 0.5, inplace: bool = False)

在训练期间,按照概率 p p p从伯努利分布中采样将输入张量的元素置0,输出张量的元素再乘缩放系数 1 1 − p \frac{1}{1-p} 1p1
inplace如果为True,输入张量会被设置为需要in-place操作:

import torch.nn as nn
import torch

m = nn.Dropout(p=0.5)
input = torch.randn(5,3)
output = m(input)

print(input)
print(output)

"""
tensor([[-1.2560, -1.3158,  0.3405],
        [ 1.4020, -1.3544, -2.8470],
        [-1.4518, -0.5322,  0.8935],
        [-1.3207,  0.9328, -0.3308],
        [-1.5071,  0.6539, -1.6561]])
tensor([[-2.5120, -2.6316,  0.0000],
        [ 0.0000, -2.7088, -0.0000],
        [-2.9037, -0.0000,  1.7870],
        [-2.6415,  1.8656, -0.6615],
        [-3.0143,  1.3078, -0.0000]])
"""

torchvision:tensor与PILImage
pytorch中tensor形状为 ( c , h , w ) (c,h,w) (c,h,w),而PILImage形状为 ( h , w , c ) (h,w,c) (h,w,c),所以在不使用torchvision的transforms时,需要注意形状的转换

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值