PyTroch学习笔记

一、基本概念类

1、与numpy有关

1、tensor与numpy转换

PS:转换后的tensor与numpy指向同一地址,所以,对一方的值改变另一方也随之改变
(1)tensor初始化,与numpy的使用方法相同

a = torch.ones(5)
print(a)

输出

tensor([1., 1., 1., 1., 1.])

(2)tensor to numpy

b = a.numpy()
print(b)

输出

[1. 1. 1. 1. 1.]
a.add_(1)
print(a)	# tensor([2., 2., 2., 2., 2.])
print(b)	# [2. 2. 2. 2. 2.]

(3)numpy to tensor

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
c = torch.add
print(a)
print(b)

输出

[2. 2. 2. 2. 2.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

除chartensor外所有tensor都可以转换为numpy

2、ToTensor:

convert a PIL image to tensor (H,W,C) in range [0,255] to a torch.Tensor(C,H,W) in the range [0.0,1.0]

tensor = transforms.ToTensor(image)

注意:当然这个图像可以是Image读取的;也可以使用opencv读取。而后者需要将读取的图像颜色通道需要从BGR转换为RGB格式。不知道为什么,虽然使用的效果相近,但两则的转换结果并不是完全一样的。

3、维度扩充

大多数模型的输入维度都是(B,C,W,H),但我们使用单张图片测试模型效果时,只有(C,W,H)三个通道的数据,此时,则需要扩充数据维度,代码如下:

"""
src-(C,W,H)
des-(B,C,W,H)
"""
des= Variable(torch.unsqueeze(src, dim=0).float(), requires_grad=False)

PS:也可按照2.2节的方式进行维度扩充

4、torch求解梯度

1、requires_grad
保存梯度
torch创建变量时,默认设置为:不自动保存梯度信息
x.requires_grad_(requires_grad = True)设置为保存梯度,输出某变量的梯度见下文。
x.requires_grad查询是否保存梯度,返回结果True或False
1、当调用backward函数时,只有requires_grad为true以及is_leaf为true的节点才会被计算梯度,即grad属性才会被赋予值。
2、在pytorch中,只有浮点类型的数才有梯度,故在方法四中指定np数组的类型为float类型。

x = torch.ones((1, 2))
print(x.requires_grad)
# False

x = torch.ones((1, 2)).requires_grad_(requires_grad = True)
print(x.requires_grad)
# True

2、微分(反向传播)
PS:out必须为标量,不能是向量,否则无法求解。
方法一:backward()方式

# out.backward() 	# 更新各变量的梯度
# x.grad 			# 输出out对于变量x的梯度
x = torch.ones((1, 2)).requires_grad_(requires_grad = True)
out = torch.mean(x * 2) 
print(out)	
# out = tensor(2., grad_fn=<MeanBackward0>)
out.backward()
print(x.grad) 		
# tensor([[1., 1.]])

方法二:autograd方式

x = torch.ones((1, 2)).requires_grad_(requires_grad = True)
out = torch.mean(x * 2)
print(torch.autograd.grad(outputs=out, inputs=x)) 	
# tensor([[1., 1.]])

当指定grad_outputs,这时计算梯度就不再需要outputs为标量了,如下

x = torch.ones((1, 2)).requires_grad_(requires_grad = True)
out = x * 2
print(torch.autograd.grad(outputs=out, inputs=x, grad_outputs=torch.ones_like(x)))
# (tensor([[2., 2.]]),)

3、backward()
每使用backward()函数反向传播一次,则相关变量的梯度累加一次(即使该变量为进行任何计算,也会将上次的梯度累加进去)。

4、torch.no_grad()
(1)更新梯度时如果直接使用如下代码,则会导致叶节点变化,由上文可知,此变量无法再保存梯度信息。

w = w - w.grad.data* lr
b = b - b.grad.data* lr

因此,需要使用下述方式进行参数更新。

5、权值更新:
1)with torch.no_grad()
使用with torch.no_grad()时,等号“=”左边的变量的requires_grad变为False,等号右边的保持不变。如果还需继续保留该变量的梯度信息,只需要在下次计算前,将其设置为True即可。如下代码:

x1 = torch.ones((1, 2), requires_grad = True)
x2 = torch.ones((1, 2), requires_grad = True)
y = torch.mean(x1 * 3)
print(y)
# tensor(3., grad_fn=<MeanBackward0>)
y.backward(retain_graph=True)
print(x1.grad)
# tensor([[1.5000, 1.5000]])
with torch.no_grad():
    x1 = x1 + x2
print(x1.requires_grad, x2.requires_grad)
print(x1, x2)
# False True
# tensor([[2., 2.]]) tensor([[1., 1.]], requires_grad=True)
x1.requires_grad = True
y = torch.mean(x1 * 3)
y.backward(retain_graph=True)
print(x1.grad)
# tensor([[1.5000, 1.5000]])

PS:no_grad()方式可以将等号左侧变量设置为False的同时将其梯度清零,如果只是单纯的使用x1.requires_grad = False则不会将梯度清零。
2)sub_()
sub_()方式更新参数后,必须将此变量的梯度清零,防止多次微分后梯度叠加。

w.data.sub_(w.grad.data*lr)
b.data.sub_(b.grad.data*1.0)
w.grad.data.zero_()
b.grad.data.zero_()

3)torch.optim
torch.optim权值更新后同样需要把变量的梯度清零。

optimizer = torch.optim.Adam([w, b], lr=0.01)

optimizer.step()
optimizer.zero_grad()

optim.Adam([w, b], lr=0.01) 需要将待更新的参数写成列表的形式
optimizer.step() 为调用一次优化,将上述选中的参数更新
optimizer.zero_grad() 梯度清零

二、内置函数

2.1 transforms模块

1、解释(定义):torchvision.transforms是pytorch中的图像预处理包,包含了很多种对图像数据进行变换的函数,这些都是在我们进行图像数据读入步骤中必不可少的。

data_transforms = transforms.Compose([
        transforms.RandomResizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ])

2、Compose方法是将多种变换组合在一起。上述对data_transforms进行了四种变换,前两个是对PILImage进行的,分别对其进行随机大小和随机宽高比的裁剪,之后resize到指定大小224,以及对原始图像进行随机的水平翻转;

第三个transforms.ToTensor()将PILImage转变为torch.FloatTensor的数据形式;

而最后一个Normalize则是对tensor进行的。

多种组合变换有一定的先后顺序,处理PILImage的变换方法(大多数方法)都需要放在ToTensor方法之前,而处理tensor的方法(比如Normalize方法)就要放在ToTensor方法之后。
3、转载文章:
pytorch中transform函数详解
PyTorch 学习笔记(三):transforms的二十二个方法

2.2 torch.squeeze() 和torch.unsqueeze()的用法

squeeze的用法主要就是对数据的维度进行压缩或者解压。
也可参考:https://blog.csdn.net/flysky_jay/article/details/81607289

1、先看torch.squeeze() 这个函数主要对数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列这种,一个一行三列(1,3)的数去掉第一个维数为一的维度之后就变成(3)行。
1)b = torch.squeeze(a) 就是将a中所有为1的维度删掉,不为1的维度没有影响。
2)b = a.squeeze(N) 就是去掉a中指定的维数为一的维度(如果该指定维度不等于1,则无效)。
3)b = torch.squeeze(a,N) a中去掉指定的定的维数为一的维度。

a = torch.ones((1, 2, 3))		# a = tensor([[[1., 1., 1.], [1., 1., 1.]]])
b = torch.squeeze(a)		# a不变, b = tensor([[1., 1., 1.], [1., 1., 1.]])
b = a.squeeze(0)			# 同上,若N=1,2则无效,即b=a
b = torch.squeeze(a,0)		# 同上,若N=1,2则无效,即b=a

2、再看torch.unsqueeze()这个函数主要是对数据维度进行扩充。给指定位置加上维数为一的维度,比如原本有个三行的数据(3),在0的位置加了一维就变成一行三列(1,3)。
1)b = a.squeeze(N) 就是在a中指定位置N加上一个维数为1的维度。
2)b = torch.squeeze(a,N) a就是在a中指定位置N加上一个维数为1的维度

三、保存和加载模型

1、加载预训练模型

可参考文章:https://blog.csdn.net/weixin_41278720/article/details/80759933

2、保存和加载自己训练过的模型及参数

pytorch的模型和参数是分开的,可以分别保存或加载模型和参数。

pytorch有两种模型保存方式:
一、保存整个神经网络的的结构信息和模型参数信息,save的对象是网络net
二、只保存神经网络的训练模型参数,save的对象是net.state_dict()

对应两种保存模型的方式,pytorch也有两种加载模型的方式。
一、对应第一种保存方式,加载模型时通过torch.load(’.pth’)直接初始化新的神经网络对象;
二、对应第二种保存方式,需要首先导入对应的网络,再通过net.load_state_dict(torch.load(’.pth’))完成模型参数的加载。

在网络比较大的时候,第一种方法会花费较多的时间。

1. 直接加载模型和参数

加载别人训练好的模型:

# 保存和加载整个模型
torch.save(model_object, 'resnet.pth')
model = torch.load('resnet.pth')

2. 分别加载网络的结构和参数

# 将my_resnet模型储存为my_resnet.pth
torch.save(my_resnet.state_dict(), "my_resnet.pth")
# 加载resnet,模型存放在my_resnet.pth
my_resnet.load_state_dict(torch.load("my_resnet.pth"))

其中my_resnet是my_resnet.pth对应的网络结构。
3. pytorch预训练模型

1)加载预训练模型和参数

resnet18 = models.resnet18(pretrained=True)
这里是直接调用pytorch中的常用模型

# PyTorch中的torchvision里有很多常用的模型,可以直接调用:
import torchvision.models as models
 
resnet101 = models.resnet18()
alexnet = models.alexnet()
squeezenet = models.squeezenet1_0()
densenet = models.densenet_161()

2)只加载模型,不加载预训练参数

# 导入模型结构
resnet18 = models.resnet18(pretrained=False)
# 加载预先下载好的预训练参数到resnet18
resnet18.load_state_dict(torch.load('resnet18-5c106cde.pth'))

3)加载部分预训练模型

resnet152 = models.resnet152(pretrained=True)
pretrained_dict = resnet152.state_dict()
"""加载torchvision中的预训练模型和参数后通过state_dict()方法提取参数
   也可以直接从官方model_zoo下载:
   pretrained_dict = model_zoo.load_url(model_urls['resnet152'])"""
model_dict = model.state_dict()
# 将pretrained_dict里不属于model_dict的键剔除掉
pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
# 更新现有的model_dict
model_dict.update(pretrained_dict)
# 加载我们真正需要的state_dict

四、出现的error

1、RuntimeError: Expected 4-dimensional input for 4-dimensional weight 64 3 3, but got 3-dimensional input of size [3, 224, 224] instead

解决方案:

img=Image.open(imgpath)        # 读取图片
img=img.resize((TARGET_IMG_SIZE, TARGET_IMG_SIZE))
tensor=img_to_tensor(img)    # 将图片转化成tensor,

print(tensor.shape)  #[3, 224, 224]
tensor = Variable(torch.unsqueeze(tensor, dim=0).float(), requires_grad=False)

print(tensor.shape)  #[1,3, 224, 224]
tensor=tensor.cuda()

2、明明写着有显存剩余,但还是会报错:CUDA out of memory. Tried to allocate 14.13 MiB (GPU 0; 6.00 GiB total capacity; 356.92 MiB already allocated; 3.99 GiB free; 6.58 MiB cached)

原因:1、多线程配置的问题,你将num_workers设置为0即可解决(我的问题通过此方法解决)
2、指定下显卡试试CUDA_VISIBLE_DEVICES=“0” (因为只有一个显卡,暂时未测试)

3、Runtime error(59):device-side assert triggered at XXX

原因:在计算指标的时候给进去的数据超出了边界。也就是说,在给出label的时候,数据集在某个标签上会给进去一个小于零或者大于类别数的一个标签,从而导致错误。
解决办法:1)检查Label是不是从0开始的;
2)检查数据集标签的类别数是否与model想对应。

发布了12 篇原创文章 · 获赞 0 · 访问量 225

猜你喜欢

转载自blog.csdn.net/weixin_43844233/article/details/102976368