pytorch 速查
Pytorch 速查手册
希望整理曾经不懂的Pytorch用法,在以后以快速得到结果
链接
- PyTorch 中,nn 与 nn.functional 有什么区别?
- Pytorch里的CrossEntropyLoss详解
- Pytorch/Numpy之squeeze
- numpy.where() 用法详解
- 加速pytorch训练- DataPrefetcher
- pytorch中分配GPU有两种方式:torch.device(好像等价于.cuda()) 和torch.cuda.set_device(gpu_id)
- PyTorch中tensor.repeat()的使用
- PyTorch使用cpu调用gpu训练的模型:使用model=DataParalle(model)训练多GPU模型后,使用CPU进行调用。
- 【python】Keras、Tensorflow 如何切换 GPU 和 CPU(强制使用CPU):
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"
- Python进度条tqdm: 设置后缀:set_postfix(),显示在右边
1 关于Tensors
自我介绍:张量的英文是Tensor,它是PyTorch里面基础的运算单位,与Numpy的ndarray相同都表示的是一个多维的矩阵。 与ndarray的最大区别就是,PyTorch的Tensor可以在 GPU 上运行,而 numpy 的 ndarray 只能在 CPU 上运行,在GPU上运行大大加快了运算速度。
在同构的意义下,第零阶张量 (r = 0) 为标量 (Scalar),在同构的意义下, (r = 1) 为向量 (Vector), 第二阶张量 (r = 2) 则称为矩阵 (Matrix),第三阶以上的统称为多维张量。
- torch.empty():构造一个不初始化的张量
x = torch.empty(5,3)
- torch.rand():返回一个张量,包含了从区间[0, 1)的均匀分布中抽取的一组随机数
x = torch.rand(5,3)
5行3列的的矩阵 - x.shape:可以使用与numpy相同的shape查看张量大小
print(x.shape)
torch.Size([2, 3]) - size():使用size()函数,效果与shape相同
x.size()
torch.Size([2, 3]) - torch.zeros() :构造全0矩阵
x = torch.zeros(5, 3, dtype=torch.long)
- torch.ones():返回一个张量,全1
x = torch.ones(2, 2)
- torch.eye():初始化一个单位矩阵,即对角线为1 其他为0,
eye=torch.eye(2,2)
- torch.randn():返回一个张量,包含了从标准正态分布(均值为0,方差为1,即高斯白噪声)中抽取的一组随机数。
torch.randn(2, 3)
- torch.linspace(start, end, steps=100, out=None) → Tensor:返回一个1维张量,包含在区间start和end上均匀间隔的step个点。
- torch.tensor():构造一个张量,直接使用数据
x = torch.tensor([5.5, 3])
- x.new_ones():基于已经存在的tensor创建一个张量
x = x.new_ones(5, 3, dtype=torch.double)
- torch.randn_like():
x = torch.randn_like(x, dtype=torch.float)
会覆盖了以前的类型 - torch.size():获取tensor的维度信息, torch.Size 是一个元组,所以它支持左右的元组操作。
x.size()
- x + y:加法
- torch.add(x, y):加法
- torch.add(x, y, out=result):加法,结果赋给result
result = torch.empty(5, 3)
- y.add_(x):把x加到y上面,直接覆盖y原来的值,以_为结尾的函数,均会改变调用值。
- x[:, 1]:输出第二列,注意索引从0开始
- torch.view():改变一个tensor的大小或者性质
x = torch.randn(4,4) # torch.Size([4, 4])
y = x.view(16) # 16维的一个list,不是矩阵了 torch.Size([16])
z = x.view(-1, 8) # the size -1 is inferred from other dimensions torch.Size([2, 8]
- a.transpose(1, 2):Swaps 2nd and 3rd dimension
a = torch.randn(1, 2, 3, 4) # torch.Size([1, 2, 3, 4])
b = a.transpose(1, 2) # torch.Size([1, 3, 2, 4])
- x.item():对于标量(零阶张量),我们可以直接使用 .item() 从中取出其对应的python对象的数值;特别的:如果张量中只有一个元素的tensor也可以调用
tensor.item
方法。
# 标量
scalar =torch.tensor(3.1433223)
print(scalar) # tensor(3.1433)
scalar.size() # torch.Size([])
scalar.item() # 3.143322229385376
# 只有一个元素的tensor,使用.item()来获得这个的value。
x = torch.randn(1)
print(x) # tensor([-1.7860])
print(x.item()) # -1.7859678268432617
print(x.size()) # torch.Size([1])
loss = (y_pred - y).pow(2).sum().item()
-
x.mm(y):张量相乘(numpy中的x.dot(y))
-
x.t():张量转置(numpy 中的x.T)
-
x.clamp(min=0):relu函数(numpy 中的np.maximum(h, 0))
-
x.clone():张量复制(numpy 中的x.copy() )
-
.pow(2):每个元素平方
loss = (y_pred - y).pow(2).sum()
-
数据类型:Tensor的基本数据类型有五种
- 32位浮点型:torch.FloatTensor。 (默认)
tensor.float()
- 64位整型:torch.LongTensor。
tensor.long()
- 32位整型:torch.IntTensor。
tensor.int()
- 16位整型:torch.ShortTensor。
tensor.short()
- 64位浮点型:torch.DoubleTensor。
- 除以上数字类型外,还有 byte和chart型
tensor.char()
tensor.byte()
- 32位浮点型:torch.FloatTensor。 (默认)
-
numpy和Tensor转换:
a = torch.randn((3, 2))
# tensor转化为numpy
numpy_a = a.numpy()
# numpy转化为Tensor
torch_a = torch.from_numpy(numpy_a)
torch_a
-
torch.max():eg:dim=1代表沿着行取最大值,
max_value, max_idx = torch.max(x, dim=1)
-
torch.sum():eg:每行 x 求和,
sum_x = torch.sum(x, dim=1)
-
x.norm(p=2,dim=1,keepdim=True):【返回输入张量给定维dim 上每行的p范数】
pytorch中x.norm(p=2,dim=1,keepdim=True)的理解
keepdim=True:运算完之后的维度和原来一样,原来是三维数组现在还是三维数组(不过某一维度变成了1);
keepdim=False:运算完之后一般少一维度,求平均变为1的那一维没有了; -
tensor.mean(axis, keepdim):axis=k,按第k维运算,其他维度不遍,第k维变为1
pytorch中tensor.mean(axis, keepdim)
print(x.mean(axis=0,keepdim=True).shape)
- @和*的用处
- @是用来对tensor进行矩阵相乘的
- *用来对tensor进行矩阵进行逐元素相乘
2 求导相关
-
requires_grad=False:在张量创建时,通过设置 requires_grad 标识为Ture来告诉Pytorch需要对该张量进行自动求导,PyTorch会记录该张量的每一步操作历史并自动计算。默认为False,如果我们想计算某些的tensor的梯度,我们只需要在建立这个tensor时加入这么一句:requires_grad=True。
x = torch.rand(5, 5, requires_grad=True)
-
x.grad:如果这个tensor x的requires_grad=True,那么反向传播之后x关于某个标量值的梯度会累积在张量 x.grad上。PyTorch会自动追踪和记录对与张量的所有操作,当计算完成后调用.backward()方法自动计算梯度并且将计算结果保存到grad属性中。
-
.grad_fn:在张量进行操作后,grad_fn会被赋予一个新的函数,这个函数引用了一个创建了这个Tensor类的Function对象。 Tensor和Function互相连接生成了一个非循环图,它记录并且编码了完整的计算历史。每个张量都有一个.grad_fn属性,如果这个张量是用户手动创建的那么这个张量的grad_fn是None。
-
with torch.no_grad()::在训练神经网络时,我们通常不希望通过权重更新步骤进行反向传播,使用
with torch.no_grad():
上下文管理器来防止构造计算图。使用上下文管理器临时禁止对已设置requires_grad=True的张量进行自动求导。这个方法在测试集计算准确率的时候会经常用到。使用.no_grad()进行嵌套后,代码不会跟踪历史记录,也就是说保存的这部分记录会减少内存的使用量并且会加快少许的运算速度。
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 反向传播后手动将梯度设置为零
w1.grad.zero_()
w2.grad.zero_()
-
with torch.set_grad_enabled(False):一个全局的环境,接下来所有的tensor运算产生的新的节点都是不可求导的;设置为True就是可以求导的了
-
loss.backward():.backward() 自动计算所有的requires_grad=True 张量的梯度,张量的梯度将累积到其
.grad
属性中。
x = torch.rand(5, 5, requires_grad=True)
x = torch.rand(5, 5, requires_grad=True)
z=torch.sum(x+y)
# 如果Tensor类表示的是一个标量(即它包含一个元素的张量),则不需要为backward()指定任何参数,但是如果它有更多的元素,则需要指定一个gradient参数,它是形状匹配的张量。 以上的 z.backward()相当于是z.backward(torch.tensor(1.))的简写。 这种参数常出现在图像分类中的单标签分类,输出一个标量代表图像的标签。
x = torch.rand(5, 5, requires_grad=True)
y = torch.rand(5, 5, requires_grad=True)
z= x**2+y**3
#我们的返回值不是一个标量,所以需要输入一个大小相同的张量作为参数,这里我们用ones_like函数根据x生成一个张量
z.backward(torch.ones_like(x))
print(x.grad)
-
↑Autograd 过程解析:Python的
dir()
返回参数的属性、方法列表。z
是一个Tensor变量,看看里面有哪些成员变量。我们直接排除掉一些Python中特殊方法(以_开头和结束的)和私有方法(以*开头的,直接看几个比较主要的属性:.is_leaf
:记录是否是叶子节点。通过这个属性来确定这个变量的类型,在官方文档中所说的“graph leaves”,“leaf variables”,都是指像x
,y
这样的手动创建的、而非运算得到的变量,这些变量成为创建变量。 像z
这样的,是通过计算后得到的结果称为结果变量。 -
.is_leaf:一个变量是创建变量还是结果变量是通过
.is_leaf
来获取的。
x = torch.rand(5, 5, requires_grad=True)
y = torch.rand(5, 5, requires_grad=True)
z= x**2+y**3
print("x.is_leaf="+str(x.is_leaf)) # x.is_leaf=True
print("z.is_leaf="+str(z.is_leaf)) # z.is_leaf=False
# x是手动创建的没有通过计算,所以他被认为是一个叶子节点也就是一个创建变量,而z是通过x与y的一系列计算得到的,所以不是叶子结点也就是结果变量。
-
为什么我们执行
z.backward()
方法会更新x.grad
和y.grad
呢?.grad_fn
属性记录的就是这部分的操作,记录并且编码了完整的计算历史。
z.grad_fn # <AddBackward0 at 0x120840a90>
# grad_fn是一个AddBackward0类型的变量
- 我们 dir(z.grad_fn),看看里面有什么东西?
next_functions
就是grad_fn
的精华!
dir(z.grad_fn) # 'next_functions',
z.grad_fn.next_functions
# ((<PowBackward0 at 0x1208409b0>, 0), (<PowBackward0 at 0x1208408d0>, 0))
# next_functions是一个tuple of tuple of PowBackward0 and int。
# 为什么是2个tuple ? 因为我们的操作是z= x**2+y**3 刚才的AddBackward0是相加,而前面的操作是乘方 PowBackward0。tuple第一个元素就是x相关的操作记录
- 继续挖掘:在PyTorch的反向图计算中,
AccumulateGrad
类型代表的就是叶子节点类型,也就是计算图终止节点。AccumulateGrad
类中有一个.variable
属性指向叶子节点。
xg = z.grad_fn.next_functions[0][0]
dir(xg) # next_functions
x_leaf=xg.next_functions[0][0]
type(x_leaf) # AccumulateGrad
x_leaf.variable # 这个.variable的属性就是我们的生成的变量x
print("x_leaf.variable的id:"+str(id(x_leaf.variable)))
print("x的id:"+str(id(x)))
# x_leaf.variable的id:4840553424
# x的id:4840553424
-
这样整个规程就很清晰了:
- 当我们执行z.backward()的时候。这个操作将调用z里面的grad_fn这个属性,执行求导的操作。
- 这个操作将遍历grad_fn的next_functions,然后分别取出里面的Function(AccumulateGrad),执行求导操作。这部分是一个递归的过程直到最后类型为叶子节点。
- 计算出结果以后,将结果保存到他们对应的variable 这个变量所引用的对象(x和y)的 grad这个属性里面。
- 求导结束。所有的叶节点的grad变量都得到了相应的更新
-
.detach():停止tensor历史记录的跟踪,该tensor与计算历史记录分离,并防止将来的计算被跟踪。
-
.grad.zero_():将梯度设置为零
w1.grad.zero_()
-
.zero_grad():手动将梯度缓冲区设置为零
optimizer.zero_grad()
-
torch.device():应该是方便GPU上运行的
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
-
torch.autograd.Function:如果需要自定义autograd扩展新的功能,就需要扩展Function类。因为Function使用autograd来计算结果和梯度,并对操作历史进行编码。定义
torch.autograd.Function
的子类并三个方法,来定义自己的自动求导运算
# __init__ (optional):如果这个操作需要额外的参数则需要定义这个Function的构造函数,不需要的话可以忽略。
# forward():执行前向传播的计算代码
# backward():反向传播时梯度计算的代码。 参数的个数和forward返回值的个数一样,每个参数代表传回到此操作的梯度。
# 调用 MyReLU.apply 函数来使用自定义的ReLU
import torch
class MyReLU(torch.autograd.Function):
# (输入参数是张量)
# 方法必须是静态方法,所以要加上@staticmethod
@staticmethod
def forward(ctx, x):
# ctx 用来保存信息这里类似self,并且ctx的属性可以在backward中调用
ctx.save_for_backward(x)
return x.clamp(min=0)
@staticmethod
def backward(ctx, grad_output):
x, = ctx.saved_tensors
grad_x = grad_output.clone()
grad_x[x < 0] =0
return grad_x
# 详见 大刀阔斧,步步推进
# 调用 MyReLU.apply 函数来使用自定义的ReLU
y_pred = MyReLU.apply(x.mm(w1)).mm(w2)
3 数据加载处理相关
相关头文件
from __future__ import print_function, division
import os
import torch
import pandas as pd #用于更容易地进行csv解析
from skimage import io, transform #用于图像的IO和变换
import numpy as np
import matplotlib.pyplot as plt
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms, utils
# 忽略警告
import warnings
warnings.filterwarnings("ignore")
plt.ion() # interactive mode 在脚本中遇到plt.show(),代码还是会继续执行
- pd.read_csv():读取csv数据
landmarks_frame = pd.read_csv('data/faces/face_landmarks.csv')
n = 65
img_name = landmarks_frame.iloc[n, 0] # 获取第65行第0列数据
landmarks = landmarks_frame.iloc[n, 1:].as_matrix() # 将第1列以后的转化为矩阵
landmarks = landmarks.astype('float').reshape(-1, 2) # 将原本一行的数据转化为两行列,一列为x坐标,y坐标
-
.iloc[n, 1:]:数据切片,获取第n行,第1列以后的数据
-
.as_matrix():转化为矩阵
-
.astype('float'):转换格式
-
.reshape(-1, 2):重塑大小
-
torch.utils.data.Dataset:表示数据集的抽象类,因此自定义的数据集应继承Dataset 并重载以下方法
__len__
:实现len(dataset)
返回数据集的尺寸__getitem__
:用索引(0
到len(self)
)获取一条数据或一个样本__init__
:读取csv的文件内容
class FaceLandmarksDataset(Dataset):
def __init__(self, csv_file, root_dir, transform=None):
self.landmarks_frame = pd.read_csv(csv_file)
self.root_dir = root_dir
self.transform = transform
def __len__(self):
return len(self.landmarks_frame) # 有多少样本(行)
def __getitem__(self, index):
img_name = os.path.join(self.root_dir, self.landmarks_frame.iloc[index, 0])
image = io.imread(img_name)
landmarks = self.landmarks_frame.iloc[index, 1:]
landmarks = np.array([landmarks])
landmarks = landmarks.astype('float').reshape(-1, 2)
sample = {'image': image, 'landmarks': landmarks}
if self.transform:
sample = self.transform(sample)
return sample
- torchvision.transforms.Compose:组合一个变换
- Resize:把给定的图片resize到given size
- transforms.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] 把[0,255]转换到[0.0, 1.0] - transforms.Normalize :Normalized an tensor image with mean and standard deviation;
- ToPILImage: convert a tensor to PIL image
- Scale:目前已经不用了,推荐用Resize
- CenterCrop:在图片的中间区域进行裁剪
- RandomCrop:在一个随机的位置进行裁剪
- RandomHorizontalFlip:以0.5的概率水平翻转给定的PIL图像
- RandomVerticalFlip:以0.5的概率竖直翻转给定的PIL图像
- RandomResizedCrop:将PIL图像裁剪成任意大小和纵横比
- Grayscale:将图像转换为灰度图像
- RandomGrayscale:将图像以一定的概率转换为灰度图像
- ColorJitter:随机改变图像的亮度对比度和饱和度。
import torch
from torchvision import transforms, datasets
data_transform = transforms.Compose([
transforms.RandomSizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
hymenoptera_dataset = datasets.ImageFolder(root='hymenoptera_data/train', transform=data_transform)
dataset_loader = torch.utils.data.DataLoader(hymenoptera_dataset,batch_size=4, shuffle=True,num_workers=4)
-
np.random.randint(0,n):获取一个随机整数
-
torch.utils.data.DataLoader:对所有数据集简单的使用for循环牺牲了许多功能,尤其是:批量处理数据、打乱数据。torch.utils.data.DataLoader是一个提供上述所有这些功能的迭代器。
# 代码一般是这么写的:
# 1. 定义学习集 DataLoader
train_data = torch.utils.data.DataLoader(hymenoptera_dataset,batch_size=4, shuffle=True,num_workers=4,各种设置...)
# 2.将数据喂入神经网络进行训练
for i, (input, target) in enumerate(train_data):
循环代码行......
# DataLoader中的几个重要参数
# dataset:(数据类型 dataset)输入的数据类型
# batch_size:(数据类型 int)每次输入数据的行数,默认为1,每次喂给神经网络多少行数据
# shuffle:(数据类型 bool)洗牌。默认设置为False。在每次迭代训练时是否将数据洗牌,默认设置是False。将输入数据的顺序打乱,是为了使数据更有独立性,但如果数据是有序列特征的,就不要设置成True了。
# num_workers:(数据类型 Int)工作者数量,默认是0。使用多少个子进程来导入数据。设置为0,就是使用主进程来导入数据。注意:这个数字必须是大于等于0的,负数估计会出错。
# drop_last:(数据类型 bool)丢弃最后数据,默认为False。设置了 batch_size 的数目后,最后一批数据未必是设置的数目,有可能会小些。这时你是否需要丢弃这批数据。
# timeout:(数据类型 numeric)超时,默认为0。是用来设置数据读取的超时时间的,但超过这个时间还没读取到数据的话就会报错。 所以,数值必须大于等于0。
- DataLoader返回的是一个可迭代对象,我们可以使用迭代器分次获取数据
dl = torch.utils.data.DataLoader(ds_demo, batch_size=10, shuffle=True, num_workers=0)
idata=iter(dl)
print(next(idata))
- 常见的用法是使用for循环对其进行遍历
for i, data in enumerate(dl):
print(i,data)
# 为了节约空间,这里只循环一遍
break
-
我们已经可以通过dataset定义数据集,并使用Datalorder载入和遍历数据集,除了这些以外,PyTorch还提供能torcvision的计算机视觉扩展包,torchvision 是PyTorch中专门用来处理图像的库,里面封装了torchvision.datasets、torchvision.models、torchvision.transforms:
- torchvision.datasets:torchvision.datasets 可以理解为PyTorch团队自定义的dataset,这些dataset帮我们提前处理好了很多的图片数据集,我们拿来就可以直接使用:
MNIST
COCO
Captions
Detection
LSUN
ImageFolder
Imagenet-12
CIFAR
STL10
SVHN
PhotoTour
# 示例如下:
import torchvision.datasets as datasets
trainset = datasets.MNIST(root='./data', # 表示 MNIST 数据的加载的目录
train=True, # 表示是否加载数据库的训练集,false的时候加载测试集
download=True, # 表示是否自动下载 MNIST 数据集
transform=None) # 表示是否需要对数据进行预处理,none为不进行预处理
- **torchvision.models**:torchvision不仅提供了常用图片数据集,还提供了训练好的模型,可以加载之后,直接使用,或者在进行迁移学习。 torchvision.models模块的子模块中包含以下模型结构。
AlexNet
VGG
ResNet
SqueezeNet
DenseNet
#我们直接可以使用训练好的模型,当然这个与datasets相同,都是需要从服务器下载的
import torchvision.models as models
resnet18 = models.resnet18(pretrained=True)
- **torchvision.transforms**:transforms 模块提供了一般的图像转换操作类,用作数据处理和数据增强
from torchvision import transforms as transforms
transform = transforms.Compose([
transforms.RandomCrop(32, padding=4), #先四周填充0,在把图像随机裁剪成32*32
transforms.RandomHorizontalFlip(), #图像一半的概率翻转,一半的概率不翻转
transforms.RandomRotation((-45,45)), #随机旋转
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.229, 0.224, 0.225)), #R,G,B每层的归一化用到的均值和方差
])
肯定有人会问:(0.485, 0.456, 0.406), (0.2023, 0.1994, 0.2010) 这几个数字是什么意思?这些都是根据ImageNet训练的归一化参数,可以直接使用,我们认为这个是固定值就可以
- torchvision.datasets.ImageFolder:torchvision包提供了常用的数据集类(datasets)和转换(transforms),你可能不需要自己构造这些类。很常用的数据集类ImageFolder。 它假定了数据集是以如下方式构造的,其中'ants’,bees’等是分类标签。
root/ants/xxx.png
root/ants/xxy.jpeg
root/ants/xxz.png
.
root/bees/123.jpg
root/bees/nsdf3.png
root/bees/asd932_.png
hymenoptera_dataset = datasets.ImageFolder(root='hymenoptera_data/train',
transform=data_transform)
-
torch.unsqueeze():对数据维度进行扩充。给指定位置加上维数为一的维度,比如原本有个三行的数据(3),在0的位置加了一维就变成一行三列(1,3)
a.unsqueeze(N)
: 就是在a中指定位置N加上一个维数为1的维度b=torch.unsqueeze(a,N)
: b就是在a中指定位置N加上一个维数为1的维度
-
torch.squeeze():对数据的维度进行压缩,去掉维数为1的的维度,比如是一行或者一列这种,一个一行三列(1,3)的数去掉第一个维数为一的维度之后就变成(3)行。
squeeze(a)
:a中所有为1的维度删掉,不为1的维度没有影响。a.squeeze(N)
:去掉a中指定的维数为一的维度b=torch.squeeze(a,N)
a中去掉指定的定的维数为一的维度。
-
torchvision.datasets:PyTorch通过torch.utils.data对一般常用的数据加载进行了封装,可以很容易地实现多线程数据预读和批量加载。 并且torchvision已经预先实现了常用图像数据集,包括前面使用过的CIFAR-10,ImageNet、COCO、MNIST、LSUN等数据集,可通过torchvision.datasets方便的调用
4 网络搭建相关
-
torch.nn:计算图和autograd是十分强大的工具,可以定义复杂的操作并自动求导;然而对于大规模的网络,autograd太过于底层,nn包中定义一组大致等价于层的模块。一个模块接受输入的tesnor,计算输出的tensor,而且还保存了一些内部状态比如需要学习的tensor的参数等。nn包中也定义了一组损失函数(loss functions),用来训练神经网络。
-
torch.nn.Sequential():nn.Sequential是包含其他模块的模块,并按顺序应用这些模块来产生其输出。nn.Sequential里面的模块按照顺序进行排列的,所以必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。
model = torch.nn.Sequential(
torch.nn.Linear(D_in, H),
torch.nn.ReLU(),
torch.nn.Linear(H, D_out)
)
y_pred = model(x) # 前向传播:通过向模型传入x计算预测的y。
model.zero_grad() # 反向传播之前清零梯度
nn.Sequential中可以使用OrderedDict来指定每个module的名字,而不是采用默认的命名方式(按序号 0,1,2,3...)。
from collections import OrderedDict
class net_seq(nn.Module):
def __init__(self):
super(net_seq, self).__init__()
self.seq = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
def forward(self, x):
return self.seq(x)
net_seq = net_seq()
- nn.ModuleList(modules=None):以list的形式储存不同 module,并自动将每个 module 的 parameters 添加到网络之中的容器。可以把任意 nn.Module 的子类 (比如 nn.Conv2d, nn.Linear 之类的) 加到这个 list 里面,方法和 Python 自带的 list 一样。但不同于一般的 list,加入到 nn.ModuleList 里面的 module 是会自动注册到整个网络上的,同时 module 的 parameters 也会自动添加到整个网络中。若使用python的list,则会出问题。
class net_modlist(nn.Module):
def __init__(self):
super(net_modlist, self).__init__()
self.modlist = nn.ModuleList([
nn.Conv2d(1, 20, 5),
nn.ReLU(),
nn.Conv2d(20, 64, 5),
nn.ReLU()
])
def forward(self, x):
for m in self.modlist:
x = m(x)
return x
for param in net_modlist.parameters():
print(type(param.data), param.size())
#<class 'torch.Tensor'> torch.Size([20, 1, 5, 5])
#<class 'torch.Tensor'> torch.Size([20])
#<class 'torch.Tensor'> torch.Size([64, 20, 5, 5])
#<class 'torch.Tensor'> torch.Size([64])
使用 Python 自带的 list
class net_modlist(nn.Module):
def __init__(self):
super(net_modlist, self).__init__()
self.modlist = [
nn.Conv2d(1, 20, 5),
nn.ReLU(),
nn.Conv2d(20, 64, 5),
nn.ReLU()
]
def forward(self, x):
for m in self.modlist:
x = m(x)
return x
for param in net_modlist.parameters():
print(type(param.data), param.size())
#None
使用 Python 的 list 添加的卷积层和它们的 parameters 并没有自动注册到我们的网络中。当然,我们还是可以使用 forward 来计算输出结果。但是如果用其实例化的网络进行训练的时候,因为这些层的parameters不在整个网络之中,所以其网络参数也不会被更新,也就是无法训练。
nn.Sequential与nn.ModuleList的区别:
- nn.Sequential内部实现了forward函数,因此可以不用写forward函数。而nn.ModuleList则没有实现内部forward函数
- nn.Sequential可以使用OrderedDict对每层进行命名
- nn.Sequential里面的模块按照顺序进行排列的,所以必须确保前一个模块的输出大小和下一个模块的输入大小是一致的。而nn.ModuleList 并没有定义一个网络,它只是将不同的模块储存在一起,这些模块之间并没有什么先后顺序可言。网络的执行顺序是根据 forward 函数来决定的。
详解PyTorch中的ModuleList和Sequential
- torch.nn.MSELoss(reduction='sum'):nn包还包含常用的损失函数的定义,这里使用平均平方误差(MSE),设置
reduction='sum'
,表示我们计算的是评分误差的‘和’,而不是平均值,reduction=‘elementwise_mean’
来使用均方误差作为损失更为常见
loss_fn = torch.nn.MSELoss(reduction='sum')
loss = loss_fn(y_pred, y)
loss.backward()
-
torch.nn.Linear():线性层
torch.nn.Linear(D_in, H)
-
torch.nn.ReLU():ReLu 激活函数
-
model.parameters():获取2中定义模型的所有参数
# 使用梯度下降更新权重。
# 每个参数都是张量,更新它的数值
with torch.no_grad():
for param in model.parameters():
param -= learning_rate * param.grad
- torch.optim:SGD、AdaGrad、RMSProp、Adam等更复杂的优化器来训练神经网络。
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 在反向传播之前,使用optimizer将它要更新的所有张量的梯度清零(这些张量是模型可学习的权重)
optimizer.zero_grad()
loss.backward() # 反向传播,根据模型参数计算loss的损失梯度
optimizer.step() # 调用Optimizer的step函数使它所有参数更新
- torch.nn.Module:需要指定比现有模块序列更复杂的模型;对于这些情况,可以通过继承nn.Module 并定义 forward 函数
class TwoLayerNet(torch.nn.Module):
def __init__(self, D_in, H, D_out):
super(TwoLayerNet, self).__init__()
self.linear1 = torch.nn.Linear(D_in, H)
self.linear2 = torch.nn.Linear(H, D_out)
def forward(self, x):
h_relu = self.linear1(x).clamp(min=0)
y_pred = self.linear2(h_relu)
return y_pred
# 通过实例化上面定义的类来构建我们的模型。
model = TwoLayerNet(D_in, H, D_out)
y_pred = model(x) # 前向传播:通过向模型传递x计算预测值y
# 详见《神经网络》
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 1 input image channel, 6 output channels, 5x5 square convolution
self.conv1 = nn.Conv2d(1, 6, 5) # 输入1个通道,输出6个通道,5×5filter
self.conv2 = nn.Conv2d(6, 16, 5) # 输入6个通道,输出16个通道,5×5filter
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# Max pooling over a (2, 2) window
x = F.max_pool2d(F.relu(self.conv1(x)), (2,2))
# If the size is a square you can only specify a single number
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # all dimensions except the batch dimension
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
- net.parameters():返回可被学习的参数(权重)列表和值
net = Net()
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight
-
nn.functional :除了nn别名以外,我们还引用了nn.functional,这个包中包含了神经网络中使用的一些常用函数,这些函数的特点是,不具有可学习的参数(如ReLU,pool,DropOut等),这些函数可以放在构造函数中,也可以不放,但是这里建议不放。
import torch.nn.functional as F
-
torch.nn.Parameter()::首先可以把这个函数理解为类型转换函数,将一个不可训练的类型Tensor转换成可以训练的类型parameter并将这个parameter绑定到这个module里面(net.parameter()中就有这个绑定的parameter,所以在参数优化的时候可以进行优化的),所以经过类型转换这个self.v变成了模型的一部分,成为了模型中根据训练可以改动的参数了。
使用这个函数的目的也是想让某些变量在学习的过程中不断的修改其值以达到最优化。与torch.tensor([1,2,3],requires_grad=True)的区别,后者只是将参数变成可训练的,并没有绑定在module的parameter列表中。
self.v = torch.nn.Parameter(torch.FloatTensor(hidden_size))
5 画图相关
头文件:
from skimage import io, transform #用于图像的IO和变换
import matplotlib.pyplot as plt
-
torchvision.utils.make_grid():将若干幅图像拼成一幅图像。其中padding的作用就是子图像与子图像之间的pad有多宽。在需要展示一批数据时很有用
-
plt.imshow(image):展示一张图,
plt.imshow(np.transpose(npimg, (1, 2, 0)))
在plt.imshow的输入的是(imagesize,imagesize,channels),img的格式为(channels,imagesize,imagesize),这两者的格式不一致,需要转换后显示,原来的1换到0的位置,原来的2换到1的位置,原来的0换到最后。 -
plt.ion():interactive mode 在脚本中遇到plt.show(),代码还是会继续执行
-
matplotlib.pyplot.scatter:散点图
matplotlib.pyplot.scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, *, data=None, **kwargs)
# x,y:表示的是大小为(n,)的数组,也就是我们即将绘制散点图的数据点
# s:是一个实数或者是一个数组大小为(n,),这个是一个可选的参数。
# c:表示的是颜色,也是一个可选项。默认是蓝色'b',表示的是标记的颜色,或者可以是一个表示颜色的字符,或者是一个长度为n的表示颜色的序列等等.
# marker:表示的是标记的样式,默认的是'o'。
-
io.imread()
-
os.path.join():
io.imread(os.path.join('data/faces/', img_name))
-
fig = plt.figure():一个画布
-
ax = plt.subplot(1, 4, i + 1):子图,1行,4列,这个是第i+1个
-
ax.set_title('Sample #{}'.format(i))
-
ax.axis('off')
-
image = image.transpose((2, 0, 1)):交换颜色轴,因为numpy包的图片是: H * W * C,torch包的图片是: C * H * W
-
plt.tight_layout(): 自动调整子图参数,使之填充整个图像区域
# 并遍历数据样本。我们将会打印出前四个例子的尺寸并展示标注的特征点。
face_dataset = FaceLandmarksDataset(csv_file='data/faces/face_landmarks.csv', root_dir='data/faces/')
fig = plt.figure()
for i in range(len(face_dataset)):
sample = face_dataset[i]
print(i, sample['image'].shape, sample['landmarks'].shape)
ax = plt.subplot(1, 4, i + 1)
plt.tight_layout()
ax.set_title('Sample #{}'.format(i))
ax.axis('off')
show_landmarks(**sample)
if i == 3:
plt.show()
break
模型相关
- 保存加载整个模型:
# save model
torch.save(model,'mymodel.pkl')
# load model
model=torch.load('mymodel.pkl')
- 仅保存加载模型参数(推荐):相比较于保存整个模型而言,仅保存模型参数的做法应该不仅节省空间,更有灵活性的优势。可以取出特定层的参数,这一点在已经训练好的模型上取与现有模型相同层的参数上应该有帮助。
# save model parameters
torch.save(model.state_dict(), 'mymodel.pkl')
# load save model parameters
model_object.load_state_dict(torch.load('mymodel.pkl'))
- 加载别的模型中相同的网络参数至新的模型:用已经训练好的网络参数作为自己模型的网络权重的初始化。下面代码实现了从
model_from
到model to
的相同网络参数的拷贝。
def transfer_weights(model_from, model_to):
wf = copy.deepcopy(model_from.state_dict()) # 对 model from中的模型参数的深度拷贝;
wt = model_to.state_dict() # 对 model to模型参数的获取
# 如果在model to中出现的网络结构,但是在model from中没有出现,那么就拷贝一份给wf。这样做的目的是让wf扩充后的结构跟wt一样,即保留了model from中的模型参数,又将结构扩充到跟 model to的一样
for k in wt.keys() :
if (not k in wf):
wf[k] = wt[k]
model_to.load_state_dict(wf) # 通过load_state_dict函数加载我们想要的模型参数到目标模型model to中
# 以上的函数要求两个模型中如果具有相同的名字,那么对应的参数大小应该是一样的。