赞
踩
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计算交叉熵的公式为:
这也正好解释了前面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
∂W∂J=∂z∂Ja
为什么要乘z前面的输出a?其实上式可以理解为:
∂
J
∂
W
=
∂
J
∂
z
∂
z
∂
W
\frac{\partial J}{\partial W}=\frac{\partial J}{\partial z}\frac{\partial z}{\partial W}
∂W∂J=∂z∂J∂W∂z
因为下一个激活前的输入值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)+(1−yi)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
C−1之间;
负对数似然损失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=1∑Nli
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=1∑Nli
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}
1−p1;
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时,需要注意形状的转换
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。