pytorch备忘录
0.前言
这篇文章是最近学习pytorch的一点经验总结,整体来说是备忘录性质的随笔。
1.GPU利用率过低(2020年7月20日)
问题描述:
跑数据时发现GPU利用率几乎为0,CPU占用倒是很高,一度怀疑是CUDA没配置好。然而做了profile之后发现大多数时间都用在了读取数据上。
读取数据是下面这个模块
batch_size=1024
num=8
class Diabetes(Dataset):
def __init__(self,path):
self.bits=np.loadtxt(path,delimiter=',',dtype=np.float32)
self.data=self.bits[:,:-1]
self.label=self.bits[:,[-1]]
self.len=self.bits.shape[0]
def __getitem__(self, item):
return self.data[item],self.label[item]
def __len__(self):
return self.len
train_loader=DataLoader(dataset=dataset,
batch_size=batch_size,
shuffle=True,
num_workers=num)
于是开始调整,发现线程数调低反而更快。num_works=8
时耗时前三的模块如下表,第一行是python中用于序列化的模块。
(num_works=8 )名称 |
调用次数 | 占用时间/ms |
---|---|---|
method 'dump' of '_pickle.Pickler' objects | 800 | 437621 |
built-in method _winapi.WaitForSingleObject | 1000 | 18122 |
built-in method _winapi.CreateProcess | 400 | 1363 |
num_works=0
时如下表:
(num_works=0 ) 名称 |
调用次数 | 占用时间/ms |
---|---|---|
method 'cuda' of 'torch._C._TensorBase' objects | 206 | 1151 |
built-in method stack | 200 | 797 |
built-in method as_tensor | 151800 | 439 |
可以看到现在主要消耗的时间在计算而不是序列化和等待 |
1.1 结论
事后思考认为可能是对于较小的数据集(这个数据集一共只有749个sample)多线程读取的效率不高,大多数时间都花在了序列化和等待上,导致读取数据变慢,但同时GPU计算速度很快,数据一来就算完了,最终使得GPU的利用率很低。
可以考虑先使用默认参数(num_works=0
),如果发现读取速度不足再考虑调高。
2.卷积神经网络tensor操作(torchsnooper工具)
现有这样一段程序,如果它是错误的,而且知道问题在于tensor的大小对不上号。
很自然的我们会想到写一堆print(x.shape)
,那么有没有更快捷的方法呢?torchsnooper就是一个这样的工具。
from torchsnooper import snoop
# pip install torchsnooper(conda的清华源中没有这个包)
@snoop() #装饰器
def forward(self,x):
x = self.sigmod(self.linear1(x))
x = self.sigmod(self.linear2(x))
x = self.sigmod(self.linear3(x))
return x
可以得到如下的输出:
Source path:... C:/Users/Jrzih/iCloudDrive/code/Conv_network/pytorch-tutorial/Dataload.py
Starting var:.. self = Net( (linear1): Linear(in_features=8, out_featu... out_features=1, bias=True) (sigmod): Sigmoid())
Starting var:.. x = tensor<(759, 8), float32, cuda:0>
23:41:40.072532 call 37 def forward(self,x):
23:41:40.073532 line 38 x = self.sigmod(self.linear1(x))
Modified var:.. x = tensor<(759, 6), float32, cuda:0, grad>
23:41:40.271252 line 39 x = self.sigmod(self.linear2(x))
Modified var:.. x = tensor<(759, 2), float32, cuda:0, grad>
23:41:40.272253 line 40 x = self.sigmod(self.linear3(x))
Modified var:.. x = tensor<(759, 1), float32, cuda:0, grad>
23:41:40.273252 line 41 return x
23:41:40.273252 return 41 return x
Return value:.. tensor<(759, 1), float32, cuda:0, grad>
Elapsed time: 00:00:00.201720
3.模型保存和读取
#--------save---------
best_loss=float('inf')
model=Net()
if loss < best_loss:
best_loss = loss
state = {'net':model.state_dict(), 'optimizer':optimizer.state_dict(), 'epoch':epoch}
torch.save(model.state_dict(), 'best_model.pth')
#-------load----------
checkpoint = torch.load(dir)
model.load_state_dict(checkpoint['net'])
optimizer.load_state_dict(checkpoint['optimizer'])
start_epoch = checkpoint['epoch'] + 1
4.tensorboard
from torch.utils.tensorboard import SummaryWriter
# 路径
writer = SummaryWriter('./log')
# 图表名 纵轴 横轴
writer.add_scalar('loss', loss.item(), epoch)
# bash中输入
tensorboard --logdir=./log --port xxxx
5.torch.nn.CrossEntropyLoss()
简析
CrossEntropyLoss(model(X),Y)
值得注意的是CrossEntropyLoss
是LogSoftmax
和nn.NLLLoss
的结合
文档原文:This criterion combines :func:nn.LogSoftmax
and :func:nn.NLLLoss
in one single class.
源码中也有体现
def cross_entropy(input, target, weight=None, size_average=None, ignore_index=-100,
reduce=None, reduction='mean'):
...
return nll_loss(log_softmax(input, 1), target, weight, None, ignore_index, None, reduction)
LogSoftmax
是Softmax的结果取对数,那nn.NLLLoss
是什么?
通过查阅资料可知,可以实现如下(源码中的计算是由torch._C._nn.nll_loss
来做的)
import torch
from torch import nn
from torch.nn import functional as F
pre=torch.tensor([[ 1.1569, 0.9973, -0.9057, -0.7047, 0.8989],
[ 0.2700, -0.7745, 0.2419, 0.2702, 1.2151],
[ 0.4620, 0.8975, -1.9262, -0.0024, 1.5386]])
label=torch.tensor([0,4,4])
#--------CrossEntropy的二维实现------
# 1.对预测矩阵取softmax
# 2.取出label对应位置的值构成行向量
# 3.取log之后取相反数并求平均值
res = F.softmax(pre,dim=1)
ans = torch.empty(label.shape)
for n,i in enumerate(res,0) :
ans[n]=i[label[n].long()]
ans = torch.mean(-torch.log(ans))
print(ans)
#----------------------------------
loss=nn.CrossEntropyLoss()
print(loss(pre,label))
顺便一提对于softmax
,如果不给出dim
,也就是沿着哪条轴计算,那么_get_softmax_dim
函数会自动给出dim
,源码如下
def softmax(input, dim=None, _stacklevel=3, dtype=None):
...
if dim is None:
dim = _get_softmax_dim('softmax', input.dim(), _stacklevel)
...
def _get_softmax_dim(name, ndim, stacklevel):
# type: (str, int, int) -> int
warnings.warn("Implicit dimension choice for {} has been deprecated. "
"Change the call to include dim=X as an argument.".format(name), stacklevel=stacklevel)
if ndim == 0 or ndim == 1 or ndim == 3:
ret = 0
else:
ret = 1
# 综合起来就是对于二维矩阵以外的输入,默认的dim都是0,即沿着第一根轴计算(对于二维矩阵来说是列的方向),而对于二维矩阵,dim=1,即行的方向。
return ret