Pytorch官方文档个人阅读总结

原文官方文档tutor:https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html

1. Introduction

1.1 数据载入

1.1.1 预加载的数据集

pytorch的默认库TorchText、TorchVision、TorchAudio都提供了默认的数据集;链接如下—

PyTorch domain libraries provide a number of pre-loaded datasets (such as FashionMNIST) that subclass torch.utils.data.Dataset and implement function specific to the particular data. They can be used to prototype and benchmark your model. You can find them here: Image Datasets, Text Datasets, and Audio Datasets

1.1.2 数据加载

数据载入部分提供了datasets和DataLoader:将原始的PILImage格式或者numpy.array格式的数据格式化为可被pytorch快速处理的张量类型

  • Dataset存储了样本和标签,
  • DataLoader包装迭代器(相当于列表可以通过 t e s t [ 0 ] [ 0 ] 、 t e s t [ 0 ] [ 1 ] test[0][0]、test[0][1] test[0][0]test[0][1]取值)通过这个函数我们在准备加载数据集使用mini-batch的时候可以使用多线程并行处理,这样可以加快我们准备数据集的速度。Datasets就是构建这个工具函数的实例参数之一。

参数说明

  • root is the path where the train/test data is stored,
  • train specifies training or test dataset,
  • transform and specify the feature and label transformationstarget_transform
from torch.utils.data import DataLoader
from torchvision import datasets
# Every TorchVision Dataset includes two arguments:
#transform and target_transform to modify the samples and labels respectively.
training_data=datasets.FashionMNIST(
    root="../data",
    train=True,
    download=True,
    transform=ToTensor(),
)
test_data=datasets.FashionMNIST(
    root="../data",
    train=False,
    download=True,
    transform=ToTensor(),
)
batch_size = 64

# 2.Create data loaders.
train_dataloader = DataLoader(training_data, batch_size=batch_size)
test_dataloader = DataLoader(test_data, batch_size=batch_size)

for images, labels in train_dataset_loader:
    # 将数据传给网络模型 

注意通道在第2位,N, C, H, W

for X, y in test_dataloader:
    print(f"Shape of X [N, C, H, W]: {X.shape}")
    print(f"Shape of y: {y.shape} {y.dtype}")
    break

1.1.3 模型数据可视化

figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3
    for i in range(1, cols * rows + 1):
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    img, label = training_data[sample_idx] 
    figure.add_subplot(rows, cols, i)
    plt.title(labels_map[label]) #提前设置的映射关系
    plt.axis("off")
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()

1.1.4 制作数据集

详细可查看

  • class CustomImageDataset(Dataset):

  • 必须实现三个函数: init, len, and getitem

class CustomImageDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None)
    :初始化包含图像、注释文件和两种转换的目录
    
    def __len__(self): function returns the number of samples in our dataset
    def __getitem__(self, idx):从 csv 数据中检索相应的标签,调用它们上面的转换字典,并返回在 tuple.

怎么结合transforms操作生成数据集

import torch.nn.functional as F
import torch
import torch 
import torch.nn as nn
from torch.autograd import Variable
import torchvision.models as models
from torchvision import transforms, utils
from torch.utils.data import Dataset, DataLoader
from PIL import Image
import numpy as np
import torch.optim as optim
import os
#torch.cuda.set_device(gpu_id)#使用GPU
learning_rate = 0.0001
#数据集的设置*****************************************************************************************************************
root =os.getcwd()+ '/data1/'#调用图像

#定义读取文件的格式
def default_loader(path):
    return Image.open(path).convert('RGB')

#首先继承上面的dataset类。然后在__init__()方法中得到图像的路径,然后将图像路径组成一个数组,这样在__getitim__()中就可以直接读取:
class MyDataset(Dataset): #创建自己的类:MyDataset,这个类是继承的torch.utils.data.Dataset
	def __init__(self,txt, transform=None,target_transform=None, loader=default_loader): #初始化一些需要传入的参数
		super(MyDataset,self).__init__()#对继承自父类的属性进行初始化
		fh = open(txt, 'r')#按照传入的路径和txt文本参数,打开这个文本,并读取内容
		imgs = []
		for line in fh: #迭代该列表#按行循环txt文本中的内
			line = line.strip('\n')
			line = line.rstrip('\n')
            # 删除 本行string 字符串末尾的指定字符,这个方法的详细介绍自己查询python
			words = line.split()
            #用split将该行分割成列表  split的默认参数是空格,所以不传递任何参数时分割空格
			imgs.append((words[0],int(words[1]))) 
            #把txt里的内容读入imgs列表保存,具体是words几要看txt内容而定 
        # 很显然,根据我刚才截图所示txt的内容,words[0]是图片信息,words[1]是lable       
		self.imgs = imgs
		self.transform = transform
		self.target_transform = target_transform
		self.loader = loader        
        
	def __getitem__(self, index):#这个方法是必须要有的,用于按照索引读取每个元素的具体内容
		fn, label = self.imgs[index] #fn是图片path #fn和label分别获得imgs[index]也即是刚才每行中word[0]和word[1]的信息
		img = self.loader(fn) # 按照路径读取图片
		if self.transform is not None:
			img = self.transform(img) #数据标签转换为Tensor
		return img,label#return回哪些内容,那么我们在训练时循环读取每个batch时,就能获得哪些内容
	
    def __len__(self): #它返回的是数据集的长度,也就是多少张图片,要和loader的长度作区分
		return len(self.imgs)
 #根据自己定义的那个MyDataset来创建数据集!注意是数据集!而不是loader迭代器


#*************数据集读取完毕**************
#图像的初始化操作
train_transforms = transforms.Compose([
    transforms.RandomResizedCrop((227,227)),
    transforms.ToTensor(),
])
text_transforms = transforms.Compose([
    transforms.RandomResizedCrop((227,227)),
    transforms.ToTensor(),
])

#数据集加载方式设置
train_data=MyDataset(txt=root+'train.txt', transform=transforms.ToTensor())
test_data = MyDataset(txt=root+'text.txt', transform=transforms.ToTensor())

#或者直接拼接数据集
all_data=train_data+test_data

#然后就是调用DataLoader和刚刚创建的数据集,来创建dataloader,这里提一句,loader的长度是有多少个batch,所以和batch_size有关
train_loader = DataLoader(dataset=train_data, batch_size=6, shuffle=True,num_workers=4)
test_loader = DataLoader(dataset=test_data, batch_size=6, shuffle=False,num_workers=4)
print('num_of_trainData:', len(train_data))
print('num_of_testData:', len(test_data))

  • getitem还可以这样写

     # 返回idx的数据和当前图片的label
     def __getitem__(self,idx):
         # idex-[0-总长度]
         # retrun images,labels
         # 将图片,label的路径取出来
         # 得到的img是这样的一个类型:'pokeman\\bulbasaur\\00000000.png'
         # 然而label得到的则是 0,1,2 这样的整形的格式
         img,label=self.images[idx],self.labels[idx]
         tf=transforms.Compose([
             lambda x:Image.open(x).convert('RGB'),  # 将t图片的路径转换可以处理图片数据
             # 进行数据加强
             transforms.Resize((int(self.resize*1.25),int(self.resize*1.25))),
             # 随机旋转
             transforms.RandomRotation(15),   # 设置旋转的度数小一些,否则的话会增加网络的学习难度
             # 中心裁剪
             transforms.CenterCrop(self.resize),   # 此时:既旋转了又不至于导致图片变得比较的复杂
             transforms.ToTensor(),
             transforms.Normalize(mean=[0.485,0.456,0.406],
             std=[0.229,0.224,0.225])
        ])
    

1.1.5 图像增强&ToTensor

区分PIL、OpenCV、Totensor

P I L 图 像 在 n p . a r r a y ( i m a g e , d t y p e = n p . f l o a t 32 ) 转 换 为 n u m p y . n d a r r a y 后 , 格 式 为 ( h , w , c ) , 像 素 顺 序 为 R G B ; O p e n C V 在 c v 2. i m r e a d ( ) 后 数 据 类 型 为 n u m p y . n d a r r a y , 格 式 为 ( h , w , c ) , 像 素 顺 序 为 B G R 。 PIL图像在 np.array(image,dtype=np.float32)转换为numpy.ndarray后,\\格式为(h,w,c),像素顺序为RGB;\\ OpenCV在cv2.imread()后数据类型为numpy.ndarray,\\格式为(h,w,c),像素顺序为BGR。 PILnp.array(image,dtype=np.float32)numpy.ndarray(h,w,c)RGBOpenCVcv2.imread()numpy.ndarray(h,w,c)BGR

  1. torchvision.transforms: 常用的图像操作,例如:随机切割,旋转,数据类型转换,

  2. torchvision.transforms.ToTensor():
    如:FashionMNIST 为PIL 图像格式,标签是整数。

    对于训练,我们需要将特征作为归一化张量,and scales the image’s pixel intensity values in the range [0., 1.]。为了进行这些转换,我们使用 ToTensor 和 Lambda

    从to_tensor()函数看到,函数接受PIL Image或numpy.ndarray,将其先由HWC转置为CHW格式,再转为float后每个像素除以255.0

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXOe1zHl-1650183834034)(pytorch/image-20220212155314169.png)]

实例

ToTensor converts a PIL image or NumPy ndarray into a FloatTensor.
首先创建一个大小为 10 的零张量(我们数据集中的标签数量)并调用 scatter_,它在标签 y 给定的索引上分配 value=1。

#图像到tensor ,numpy 数组到tensor , tensor 到图像等
ds = datasets.FashionMNIST(
        root="data",
        train=True,
        download=True,
        transform=ToTensor(),
        target_transform=Lambda(lambda y: torch.zeros(10, dtype=torch.float).scatter_(0, torch.tensor(y), value=1))

API

1.2 定义模型

1.2.1 定义模型

class NeuralNetwork(nn.Module):#继承nn.Module;nn.Module的子类在forward()函数中实现对输入数据的操作
    def __init__(self):
    def forward(self, x): #只需要直接输入model(X);不需要直接调用model.forward()
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10)
        )

        def forward(self, x):
            x = self.flatten(x)
            logits = self.linear_relu_stack(x)
            return logits

1.2.2 模型的层次:

  • nn.Flatten:保留通道信息

    convert each 3D 3x28x28 image into a contiguous array of 3x784 pixel values

  • nn.Linear:nn.Linear(in_features=28*28, out_features=20)

  • nn.Sequential:

    模块的有序容器 You can use sequential containers to put together a quick network like seq_modules.

  • nn.Softmax:
    softmax = nn.Softmax(dim=1)
    pred_probab = softmax(logits)

    ​ #logits 是一个向量,下一步被投给 softmax操作

神经网络的最后一个线性层返回logits -原始值[-inf, inf] -这些值被传递给神经网络。
Softmax模块,logits被缩放为值[0,1],表示模型对每个类的预测概率。Dim参数表示值之和必须为1的维度

1.2.3 模型参数

获取模型参数:
nn.Module 自动跟踪模型对象中定义的所有字段,
使用模型的 parameters() 或 named_parameters() 方法使所有参数都可以访问

print("Model structure: ", model, "\n\n")
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")

1.2.4 获取GPU:

  1. 获取设备:

    device = "cuda" if torch.cuda.is_available() else "cpu"
    
  2. 将模型放入GPU:

    model = NeuralNetwork().to(device)
    

1.3 定义损失和优化

损失函数:注意区分softmax和交叉熵,softmax只是特例

loss_fn=nn.CrossEntropyLoss()#多分类的交叉熵

优化器:我们通过注册需要训练的模型参数,并传入学习率超参数来初始化优化器。torch.optim.

 optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

在训练循环中,优化分三个步骤进行:

这三个函数的作用是先将梯度归零(optimizer.zero_grad()),然后反向传播计算得到每个参数的梯度值(loss.backward()),最后通过梯度下降执行一步参数更新(optimizer.step())

  1. 调用 optiizer.zero_grad()重置模型参数的梯度。缺省情况下,梯度是累加的; 为了防止重复计算,我们在每次迭代时显式地将它们归零。
  2. 通过调用loss.backward()反向传播预测损失
  3. 一旦我们有了梯度,我们就调用 optimizer.step()来通过在后向传递中收集的梯度来调整参数

1.4 模型训练、测试、预测:

  • 训练:在训练函数加了model.train()
    ​ model.train() #启动batch normalization和dropout

  • 测试和预测加上:
    model.eval()

    神经网络沿用batch normalization,不再使用dropout
    ​ with torch.no_grad():此是不会对w等系数求导

1.4.1 训练

def train(dataloader,model,loss_fn,optimizer):
    size=len(dataloader.dataset)
    model.train() #启动batch normalization和drop out
    for batch,(X,y) in enumerate(dataloader):
        X,y=X.to(device),y.to(device)
        pred=model(X)
        loss=loss_fn(pred,y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        if batch % 100==0:
            loss,current=loss.item(),batch*len(X)
            print(f'loss:{loss:>7f}[{current:>5d}/{size:>5d}]')

1.4.2 验证

def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

epochs = 5
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
print("Done!")

1.4.3 预测

// 预测
classes = [
    "T-shirt/top",
    "Trouser",
    "Pullover",
    "Dress",
    "Coat",
    "Sandal",
    "Shirt",
    "Sneaker",
    "Bag",
    "Ankle boot",
]
model.eval()
x,y=test_data[0][0],test_data[0][1]
with torch.no_grad():
model.eval()
x, y = test_data[0][0], test_data[0][1]
with torch.no_grad():
    pred = model(x)
    predicted, actual = classes[pred[0].argmax(0)], classes[y]
    print(f'Predicted: "{predicted}", Actual: "{actual}"')

1.5 保存模型

  1. 将模型序列化:保存模型参数
    PyTorch models store the learned parameters in an internal state dictionary, called state_dict

    torch.save(model.state_dict(), "model.pth")     
    
  2. 加载模型:重新创建模型和加载状态字典

    model = NeuralNetwork()     
    model.load_state_dict(torch.load("model.pth"))
    
  3. 跳过参数直接保存模型参数和结构并加载:这种方法在序列化模型时使用 Python pickle 模块,因此它依赖于在加载模型时可用的实际类定义。

    本质上还是需要引入参数

     torch.save(model, 'model.pth')
     model = torch.load('model.pth')
    

1.6 Tensor

1.6.1 基本属性和申请GPU

默认设备是cpu:
Tensor的属性里面有device和dtype

申请GPU

if torch.cuda.is_available():   
tensor=tensor.to('cuda')

1.6.2 其他操作:

  • 切片操作:tensor[:,1]=0

  • cat: concatenate a sequence of tensors along a given dimension.

    t1 = torch.cat([tensor, tensor, tensor], dim=1) # 列上相加
    
  • 点乘:

    print(f"tensor.mul(tensor) \n {tensor.mul(tensor)} \n")
    print(f"tensor * tensor \n {tensor * tensor}")
    
  • 矩阵乘法:

    print(f"tensor.matmul(tensor.T) \n {tensor.matmul(tensor.T)} \n")
    print(f"tensor @ tensor.T \n {tensor @ tensor.T}")
    
  • 就地操作:可以节约内存;具有_后缀的操作为就地操作。

    x.copy_(y), x.t_() #改变x
    print(tensor, "\n")
    tensor.add_(5)
    

    x.copy_(y) # 将y的内容复制到tensor中并返回这个tensor位x

  • 节省内存:+=

    before = id(X)
    X += Y
    id(X) == before
    

1.6.3 和numpy的桥梁:

此时tensor和Numpy array都在CPU,共享内存地址相同改变

  • tensor->numpy:

    t = torch.ones(5)
    print(f"t: {t}")
    n =t.numpy()
    print(f"n: {n}")
    # t的改变t会改变n
    t.add_(1) 
    print(f"t: {t}")
    print(f"n: {n}")     
    
  • numpy->tensor:

    n = np.ones(5)
    t = torch.from_numpy(n)
    #同样因为共享内存改变了NUmpy会改变tensor
    np.add(n, 1, out=n)
    print(f"t: {t}")
    print(f"n: {n}")
    

1.7 Torch.autograd

1.7.1 torch.autograd

为计算梯度,PyTorch的一个内置的区分引擎 。torch.autograd支持任意计算图的梯度自动计算。

requires_grad 属性支持梯度计算;可以在创建张量时设置

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

loss.grad_fn:我们用来构造计算图的张量函数实际上是类函数的对象。这个对象知道如何向前计算函数,以及如何在向后传播步骤中计算函数的导数。

print('Gradient function for w =', w.grad_fn)

1.7.2 loss.backward()

为了计算这些导数,我们调用loss.backward(),然后从 w.grad 和 b.grad 中检索值:

loss.backward()
print(w.grad)
#tensor([[0.1022, 0.2449, 0.2776],
#        [0.1022, 0.2449, 0.2776],
#        [0.1022, 0.2449, 0.2776],
#        [0.1022, 0.2449, 0.2776],
#        [0.1022, 0.2449, 0.2776]])
print(b.grad)#tensor([0.1022, 0.2449, 0.2776])
  • 出于性能方面的考虑,我们只能在给定的图上使用向后一次的梯度计算。如果我们需要对同一个图执行多个向后调用,我们需要将 retain_graph = True 传递给向后调用。

    假如你需要执行两次backward,先执行第一个的backward,再执行第二个backward

    loss1.backward(retain_graph=True)# 这里参数表明保留
    loss2.backward() 
    

    #backward后的中间参数。不加那么计算图x-y-z结构就被释放了,需要retain_graph参数为True去保留中间参数从而两个loss的backward()不会相互影响。

    #执行完这个后,所有中间变量都会被释放,以便下一次的循环

    #如果是在训练网络用optimizer.step(),更新参数

  • 我们**只能得到计算图的叶子节点的梯度属性,这些叶子节点需要梯度属性设置为 True。**对于我们图中的所有其他节点,梯度将不可用。

1.7.3 禁用梯度更新

所有带 require _ grad = True 的张量都将跟踪它们的计算历史和支持梯度计算。

  1. 禁用是预先训练的网络进行微调的一个非常常见的场景
  2. 禁用后只进行正向传递的情况下加快计算速度,因为在不跟踪梯度的张量上的计算将更加有效。

禁用的方式为了取消跟踪通过网络进行前向计算。我们可以通过:

  1. 法一:在计算代码周围加上torch.no_grad()来停止跟踪计算

    z = torch.matmul(x, w)+b
    print(z.requires_grad)         #true
    with torch.no_grad():
        z = torch.matmul(x, w)+b
        print(z.requires_grad)          #false
    
  2. 法二:在张量上使用 detach() 方法:

    z = torch.matmul(x, w)+b
    z_det = z.detach()
    print(z_det.requires_grad)
    

1.7.4.更多关于计算图:

  1. autograd 在一个由函数对象组成的有向无环图(DAG)中保存数据(张量)和所有执行的操作(以及由此产生的新张量)的记录。
  2. 在PyTorch中,DAG是动态的。在每次.backward()调用之后,autograd开始填充一个新的图。
    这正是允许你在模型中使用控制流语句的原因;如果需要,您可以在每次迭代中更改形状、大小和操作。
  3. 在向前传球时,autograd会同时做两件事:
    • 运行请求的操作来计算结果张量
    • 在DAG中保持操作的梯度函数。
  4. 当.backward ()被调用到 DAG 根目录. 向后传递就开始了:
    • 计算每个.grad_fn()的梯度,
    • 将它们累加到各自张量的.grad属性中
    • 利用链式法则,一直传播到叶张量。

注意,当我们使用相同的参数第二次向后调用时,梯度的值是不同的。这是因为在做反向传播时,PyTorch会对梯度进行累加,
即计算出的梯度的值被添加到计算图的所有叶子节点的grad属性中。如果你想计算正确的梯度,你需要在此之前将grad属性归零。

loss.zero_grad()

2. Dos

2.1 自动混合精度训练

结合torch.cuda.amp.autocast torch.cuda.amp.GradScaler 一起训练

2.1.1 autocast

对选定的区域进行autocast操作;

自动为GPU操作选择精度,在保持精度的同时提高性能。

2.1.2 GradScaler

prevent underflow(float16的值可能无法表示小幅度的梯度变化)

有助于方便地执行梯度缩放的步骤,梯度缩放通过最小化梯度下溢来改进具有梯度的网络的收敛性,保证梯度值具有较大的幅度,因此它们不会刷新为零

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值