PyTorch了解与安装
PyTorch是利用深度学习进行科学研究的重要工具,学术界常用的深度学习框架,简洁、高效、扩展性好。
PyTorch的安装
- 安装Anaconda
- 创建虚拟环境(Windows在Anaconda Prompt进行)
conda create -n pytorch python=3.8
- 查看环境是否安装成功
conda info --envs
- 进入创建的pytorch环境
conda activate pytorch
- 安装pytorch,在PyTorch官网https://pytorch.org/寻找安装命令代码(根据自己的安装版本选择)
- 测试PyTorch
安装的是CPU版本的话会返回False,能够调用GPU的会返回True
但之后用jutpter notebook时出现了以下问题:
解决方法:参考了安装PyTorch后jupyter notebook中仍出现“No module named torch“,
Anaconda虚拟环境安装PyTorch并使用Spyder
,操作之后即可成功运行
激活环境命令
conda activate pytorch
退出当前环境
conda deactivate
安装包
conda install package_name
卸载包
conda remove package_name
显示所有安装的包
conda list
PyTorch基础知识
张量(Tensor)
- 任何维度的数据表示
- PyTorch运算的基本单元,但并非是PyTorch中才有的概念
- 在基础数据定义和运算中会频繁用到张量
- 在PyTorch中支持GPU运算,自动求导等操作
创建tensor
import torch
x = torch.rand(4,3) #随机初始化矩阵
y = torch.zeros(4,3,dtype = torch.long) #全0矩阵的构建,并且通过dtype设置数据类型为 long
z = torch.zero_(x) #将现有矩阵转换为全0矩阵,torch.zeros_like()也是此用法
x = torch.tensor([5,6]) #直接使用数据构造张量
print(x)
#基于已经存在的tensor,创建一个tensor
x = x.new_ones(4, 3, dtype=torch.double) #创建一个新的全1矩阵tensor,返回的tensor默认具有相同的torch.dtype和torch.device
print(x)
x = torch.randn_like(x, dtype=torch.float) # 重置数据类型
print(x)
常见的构造Tensor方法:
函数 | 功能 |
---|---|
Tensor(sizes) | 基础构造函数 |
tensor(data) | 类似于np.array |
ones(size) | 全1 |
zeros(size) | 全0 |
eye(sizes) | 对角为1,其余为0 |
arange(s,e,step) | 从s到e,步长为step |
linspace(s,e,steps) | 从s到e,均匀分成step份 |
rand/randn(sizes) | rand是[0,1)均匀分布;randn是服从N(0,1)的正态分布 |
normal(mean,std) | 正态分布(均值为mean,标准差是std) |
randperm(m) | 随机排列 |
张量的操作
- 加法操作
print(x+y) #方式一
print(torch.add(x,y)) #方式二
y.add_(x) #方式三
-
索引操作(类似于numpy)
需要注意的是:索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。如果不想修改,可以考虑使用copy()等方法 -
维度变换
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # -1是指这一维的维数由其他维度决定
print(x.size(), y.size(), z.size())
torch.view() 返回的新tensor与源tensor共享内存(其实是同一个tensor),更改其中的一个,另外一个也会跟着改变。
为了使创建的张量和原始张量不共享内存,我们先用 clone() 创造一个张量副本然后再使用 torch.view()进行函数维度变换 。
- 取值操作
可以使用 .item() 来获得这个 value,而不获得其他性质
广播机制
当对两个形状不同的 Tensor 按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个 Tensor 形状相同后再按元素运算。
自动求导
- 所有神经网络的核心是autograd包,为张量上的所有操作提供了自动求导机制。
- torch.Tensor 是autograd包的核心类。如果设置它的属性 .requires_grad 为 True(默认为False),那么它将会追踪对于该张量的所有操作。
- 当完成计算后可以通过调用 .backward(),来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性
from __future__ import print_function
import torch
x = torch.randn(3,3,requires_grad=True) ##创建一个张量并设置requires_grad=True用来追踪其计算历史
print(x.grad_fn) #每个张量都有一个.grad_fn属性,该属性引用了创建 Tensor 自身的Function,如果张量是用户手动创建的,这个张量的grad_fn是 None
#如果需要计算导数,可以在 Tensor 上调用 .backward()。如果 Tensor 是一个标量(即它包含一个元素的数据),则不需要为 backward() 指定任何参数,但是如果它有更多的元素,则需要指定一个gradient参数,该参数是形状匹配的张量
y == x**2 #y是计算的结果,所以它有grad_fn属性
y.backward(gradient=torch.randn(3,3))
z = y * y * 3
out = z.mean()
out.backward() #反向传播,因为 out 是一个标量,因此out.backward()和 out.backward(torch.tensor(1.)) 等价
print(x.grad) #输出导数 d(out)/dx
#再来反向传播一次
out2 = x.sum()
out2.backward()
print(x.grad)
out3 = x.sum()
x.grad.data.zero_() #grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。
out3.backward()
print(x.grad)
#防止跟踪
with torch.no_grad():
print((x**2).requires_grad)
##希望修改tensor的数值,但又不希望autograd记录(即不会影响反向传播),可以对 tensor.data 进行操作
x = torch.ones(1,requires_grad=True)
print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但是已经是独立于计算图之外
y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播
y.backward()
print(x.data) # 更改data的值也会影响tensor的值
print(x.grad)
并行计算简介
- CUDA是NVIDIA提供的GPU并行计算框架
- 在PyTorch使用 CUDA表示要开始要求我们的模型或者数据开始使用GPU了。
- .cuda() 时,其功能是让我们的模型或者数据从CPU迁移到GPU(0)当中,通过GPU开始计算。
- 数据在GPU和CPU之间进行传递时会比较耗时,应尽量避免数据的切换
- 当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0),这将会导致爆出out of memory的错误。我们可以通过以下两种方式继续设置。
#方法一
import os
os.environ["CUDA_VISIBLE_DEVICE"] = "2" # 设置默认的显卡
#方法二
CUDA_VISBLE_DEVICE=0,1 python train.py # 使用0,1两块GPU
常见的并行方法
- 网络结构分布到不同的设备中(Network partitioning):将模型的各个部分拆分,将不同的部分放入到GPU来做不同任务的计算。但是GPU之间的通信在这种密集任务中很难办到,所以这个方式慢慢淡出了视野
- 同一层的任务分布到不同数据中(Layer-wise partitioning):同一层的模型做一个拆分,让不同的GPU去训练同一层模型的部分任务。但是在我们需要大量的训练,同步任务加重的情况下,会出现和第一种方式一样的问题。
- 不同的数据分布到不同的设备中,执行相同的任务(Data parallelism):同一个模型在不同GPU中训练一部分数据,然后再分别计算一部分数据之后,只需要将输出的数据做一个汇总,然后再反传。
PyTorch的主要组成模块
深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。
参数设置
batch_size = 16 # 批次的大小
lr = 1e-4 # 优化器的学习率
max_epochs = 100 #训练次数
GPU的设置有两种常见的方式:
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'
# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
数据读入
PyTorch数据读入是通过Dataset(定义好数据的格式和数据变换形式)+DataLoader(用iterative的方式不断读入批次数据)的方式完成的
主要包含三个函数:
-
_init_: 用于向类中传入外部参数,同时定义样本集
-
_getitem_: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据
-
_len_: 用于返回数据集的样本数
构建Dataset类的方式:
import torch
from torchvision import datasets
train_data = datasets.ImageFolder(train_path, transform=data_transform) #PyTorch自带的ImageFolder类的用于读取按一定结构存储的图片数据(path对应图片存放的目录,目录下包含若干子目录,每个子目录对应属于同一个类的图片)
val_data = datasets.ImageFolder(val_path, transform=data_transform) #data_transform”可以对图像进行一定的变换,如翻转、裁剪等操作,可自己定义
需要自己来定义Dataset类:
class MyDataset(Dataset):
def __init__(self, data_dir, info_csv, image_list, transform=None):
"""
Args:
data_dir: path to image directory.
info_csv: path to the csv file containing image indexes
with corresponding labels.
image_list: path to the txt file contains image names to training/validation set
transform: optional transform to be applied on a sample.
"""
label_info = pd.read_csv(info_csv)
image_file = open(image_list).readlines()
self.data_dir = data_dir
self.image_file = image_file
self.label_info = label_info
self.transform = transform
def __getitem__(self, index):
"""
Args:
index: the index of item
Returns:
image and its labels
"""
image_name = self.image_file[index].strip('\n')
raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
label = raw_label.iloc[:,0]
image_name = os.path.join(self.data_dir, image_name)
image = Image.open(image_name).convert('RGB')
if self.transform is not None:
image = self.transform(image)
return image, label
def __len__(self):
return len(self.image_file)
构建好Dataset后,就可以使用DataLoader来按批次读入数据了:
from torch.utils.data import DataLoader
train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
#样本是按“批”读入的,batch_size就是每次读入的样本数
#num_workers:有多少个进程用于读取数据
#shuffle:是否将读入的数据打乱
#drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练
PyTorch中的DataLoader的读取可以使用next和iter来完成:
import matplotlib.pyplot as plt
images, labels = next(iter(val_loader))
print(images.shape)
plt.imshow(images[0].transpose(1,2,0))
plt.show()
模型构建
构造多层感知机:
import torch
from torch import nn #Module 类是 nn 模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型
class MLP(nn.Module):
# 声明带有模型参数的层,这里声明了两个全连接层
def __init__(self, **kwargs): #用于创建模型参数
# 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
super(MLP, self).__init__(**kwargs)
self.hidden = nn.Linear(784, 256)
self.act = nn.ReLU()
self.output = nn.Linear(256,10)
# 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
def forward(self, x):
o = self.act(self.hidden(x))
return self.output(o)