Pytorch深度学习入门 | 系列(一):基础工具的使用

Pytorch深度学习入门 | 系列(一)

☀️教程:B站我是土堆
☀️链接:https://www.bilibili.com/video/BV1hE411t7RN?p=1


1 Python学习中的两大法宝函数

理解package结构以及dir()和help()函数

Pytorch可以比喻为一个工具箱,里面有很多分区,每个分区的工具有不同的作用

dir()函数的作用:查看对象内的所有的属性和方法,返回包含查询对象的所有属性和方法名称的列表。(能让我们知道工具包以及工具包中的分隔区有什么东西)

help()函数的作用:help()函数帮助我们了解模块、类型、对象、方法、属性的详细信息,例如告诉我们这个函数的作用等等。(能让我们知道每个工具是如何使用的,即工具的使用方法)

举例:查看torch包下的is_available()函数用法

在这里插入图片描述

注:在使用help查询函数用法的时候,输入函数名即可,不用带(),否则查询的是这个函数的返回值的信息

2 Pycharm与Jupyter使用对比

如果代码是以块为一个整体运行的话

Pycharm中的Python文件:Python文件的块是文件中的所有行的代码

  • 优点:通用,传播方便适用于大型项目
  • 缺点:如果报错则需要从头开始运行

Pycharm中的控制台:是以任意行为块运行的,适合调试代码

  • 优点:可以显示每个变量的属性
  • 缺点:不利于代码的修改,如果出错的话,报的错不会消失,影响代码的可读性

Jupyter:以任意行为块运行的

  • 优点:利于代码的阅读和修改,适用于小型项目
  • 缺点:环境需要配置

3 Pytorch中如何加载数据

3.1 Dataset类

假设我们拥有的数据是一堆“垃圾”,Dataset类可以提供一种方式去获取“垃圾”中的有用的数据及其lable,整理到一起。

该类解决的两个问题:

  • 如何获取每一个数据及其label
  • 告诉我们一共有多少个数据

Dataset类是一个抽象类,我们需要编写自己的Dataset类并继承它,主要需要重写其中的两个方法:get_item()(必须重写)和 len() (选择重写)

实战代码演示:

当文件名就是对应的label时,代码可以这么写

from torch.utils.data import Dataset
from PIL import Image
import os


class MyData(Dataset):
    """
        该数据集的文件结构如下:
        -------该项目
            ------dataset文件夹
                -----train文件夹
                    -----ants文件夹
                    -----bees文件夹
                -----val文件夹
                    -----ants文件夹
                    -----bees文件夹
    """

    def __init__(self, root_dir, label_dir):
        # 根目录 对应到train文件夹
        self.root_dir = root_dir
        # 标签目录就是数据文件名
        self.label_dir = label_dir
        # 数据地址就是根目录 + 标签目录(数据文件),也就是对应的ants文件夹或bees文件夹
        self.path = os.path.join(self.root_dir, self.label_dir)
        # 图片地址就是数据文件下的所有文件名 (返回一个列表)
        self.img_path = os.listdir(self.path)

    # 重写__getitem__()函数
    def __getitem__(self, idx):
        imag_name = self.img_path[idx]
        img_item_path = os.path.join(self.path, imag_name)
        img = Image.open(img_item_path)
        label = self.label_dir
        return img, label

    # 重写 __len__()函数:获取数据集的数量
    def __len__(self):
        return len(self.img_path)


# 测试
root_dir = "hymenoptera_data/train"
ants_label_dir = "ants"
bees_label_dir = "bees"
ants_dataset = MyData(root_dir, ants_label_dir)
bees_dataset = MyData(root_dir, bees_label_dir)

train_dataset = ants_dataset + bees_dataset
img, label = train_dataset[0]
img.show()
print(label)

当label放在一个专属的label文件夹中,label文件夹中存放着和数据文件夹中文件名字相对应的txt文件时,代码可以这么写,读出对应图片文件的label

from torch.utils.data import Dataset
from PIL import Image
import os


class MyData(Dataset):
    """
        该数据集的文件结构如下:
        -------该项目
            ------dataset文件夹
                -----train文件夹
                    -----ants_image文件夹
                    -----ants_label文件夹(其中存放的文件的文件名与ants_image文件夹中的文件名一一对应)
                    -----bees_image文件夹
                    -----bees_label文件夹
                -----val文件夹
                    -----ants_image文件夹
                    -----ants_label文件夹(其中存放的文件的文件名与ants_image文件夹中的文件名一一对应)
                    -----bees_image文件夹
                    -----bees_label文件夹
    """

    def __init__(self, root_dir, label):
        # 根目录 对应到train文件夹
        self.root_dir = root_dir
        # 标签目录,对应到对应的标签文件夹
        self.label_dir = os.path.join(root_dir, label + "_label")
        # 数据地址就是根目录 + 标签目录(数据文件),也就是对应的ants文件夹或bees文件夹
        self.path = os.path.join(self.root_dir, label + "_image")
        # 图片地址就是数据文件下的所有文件名 (返回一个列表)
        self.img_path = os.listdir(self.path)

    # 重写__getitem__()函数
    def __getitem__(self, idx):
        # 对应图片文件的名字 使用于数据文件夹和标签文件夹
        imag_name = self.img_path[idx]
        # 图片的地址
        img_item_path = os.path.join(self.path, imag_name)
        # 读出图片
        img = Image.open(img_item_path)
        # 读出该图片标签文件中的标签
        with open(os.path.join(self.label_dir, self.img_path[idx].split('.')[0] + ".txt"), 'r') as f:
            label = f.read()

        return img, label

    # 重写 __len__()函数:获取数据集的数量
    def __len__(self):
        return len(self.img_path)


# 测试
root_dir = "dataset/train"
ants_label = "ants"
bees_label = "bees"
ants_dataset = MyData(root_dir, ants_label)
bees_dataset = MyData(root_dir, bees_label)

train_dataset = ants_dataset + bees_dataset
img, label = train_dataset[200]
img.show()
print(label)

至此,以上编写的类的作用,就是可以读取数据集中的数据并读出该数据对应的标签,实际上就是提供一个接口可以去访问我们的数据集。

3.2 Dataloader类

此节可在学习完第六章后返回来看

由于每个神经网络接收的数据形式不同,该类可以为后面的网络提供不同的数据形式,也就是将数据包装加载到神经网络中。例如,神经网络读数据时一次读多少,随机读还是顺序读等等。

torch.utils.data.DataLoader(dataset, batch_size, shuffle, sampler, num_workers, drop_last, .......)
  • dataset:我们使用的数据集
  • batch_size:每个批次加载多少个数据
  • shuffle:每轮读数据是否打乱上一轮读的顺序,这里的一轮指的是epoch,也就是将dataloader完整的读取一遍
  • num_workers:多少个子进程一起加载数据,默认为0代表只使用主进程
  • drop_last:如果数据集大小不能被批处理大小整除,是否舍弃掉最后不够batch_size大小的数据

这里介绍一下batch_size和epoch:

每次从总的数据集中随机抽取出小部分数据来代表整体,基于这部分数据计算梯度和损失来更新参数,这种方法被称作随机梯度下降法(Stochastic Gradient Descent,SGD),核心概念如下:

  • minibatch:每次迭代时抽取出来的一批数据被称为一个minibatch。
  • batch size:每个minibatch所包含的样本数目称为batch size。
  • epoch:当程序迭代的时候,按minibatch逐渐抽取出样本,当把整个数据集都遍历到了的时候,则完成了一轮训练,也叫一个Epoch(轮次)。启动训练时,可以将训练的轮数num_epochsbatch_size作为参数传入。

代码示例:

import torchvision.datasets
from torch.utils.data import DataLoader

# 准备测试的数据集
test_data = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor())

test_loader = DataLoader(dataset=test_data, batch_size=4, shuffle=True, num_workers=0, drop_last=False)

# 测试数据集中第一张图片及target
img, target = test_data[0]
print(img.shape)
print(target)

# 像上面那样,dataloader每次会按照batch_size大小取出相应数量的img和target并合并组合成imgs和targets返回
for data in test_loader:
    imgs, targets = data
    print(imgs.shape)
    print(targets)

test_loader是一个可迭代对象,用for循环取数据的时候,每次取的数据量是batch_size决定的,运行结果如下:

在这里插入图片描述

可以看到,imgs中img的数量为4,每个img都是(3, 32, 32)的,targets的数量同样也是4

接下来我们修改代码,使用TensorBoard来观察:

import torch.utils.tensorboard
import torchvision.datasets
from torch.utils.data import DataLoader

# 准备测试的数据集
test_data = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor())

test_loader = DataLoader(dataset=test_data, batch_size=64, shuffle=True, num_workers=0, drop_last=False)

writer = torch.utils.tensorboard.SummaryWriter("logs_dataloader")
step = 0
# 像上面那样,dataloader每次会按照batch_size大小取出相应数量的img和target并合并组合成imgs和targets返回
for data in test_loader:
    imgs, targets = data
    # print(imgs.shape)
    # print(targets)
    # 添加一组图片可以使用add_images()
    writer.add_images("test_data", imgs, step)
    step = step + 1

writer.close()

打开TensorBoard可以观察到结果:

在这里插入图片描述

每次取数据的时候,取了64张图片

因为我们把shuffle设置为True,所以我们每轮读dataloader的时候,顺序是打乱的而不是和上一轮一样的顺序来读:

# 将dataloader完整的读两轮
for epoch in range(2):
    # 像上面那样,dataloader每次会按照batch_size大小取出相应数量的img和target并合并组合成imgs和targets返回
    for data in test_loader:
        imgs, targets = data
        writer.add_images("Epoch: {}".format(epoch), imgs, step)
        step = step + 1

打开TensorBoard可以观察到结果:

在这里插入图片描述

可以看到,读的顺序完全不同,一般情况下,我们都会设置shuffle为True

在平时我们使用Dataloader时,我们每次读出的imgs就可以作为神经网络的输入

4 TensorBoard的使用

TensorBoard 是Google开发的一个机器学习可视化工具。其主要用于记录机器学习过程,例如:

  • 记录损失变化、准确率变化
  • 记录图片变化、语音变化、文本变化等,例如在做GAN时,可以过一段时间记录一张生成的图片
  • 绘制模型

TensorBoard主要提供三个API:

  • SummaryWriter:这个用来创建一个log文件,TensorBoard面板查看时,也是需要选择查看那个log文件。
  • add_something: 向log文件里面增添数据。例如可以通过add_scalar增添折线图数据,add_image可以增添图片。
  • close:当训练结束后,我们可以通过close方法结束log写入。

4.1 TensorBoard的安装

一开始可能没有装tensorboard,我们直接pip install tensorboard装的话可能会出现setuptools版本过高而Pytorch的版本太低的情况,所以我们需要pip uninstall setuptools并重新安装版本低的conda install setuptools==58.0.4即可解决

4.2 SummaryWriter类的介绍

SummaryWriter 类提供了一个高级 API,将条目直接写入 log_dir 中的事件文件以供 TensorBoard 使用,用于在给定目录中创建事件文件,并向其中添加摘要和事件。 该类异步更新文件内容。 这允许训练程序调用方法以直接从训练循环将数据添加到文件中,而不会减慢训练速度。

代码示例:

from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs") # logs代表要穿件的目录的名字

# write.add_image()

# y = x
for i in range(100):
    writer.add_scalar("y = x", i, i)  # scalar_value是纵坐标y轴上的数据 global_step是横坐标x轴上的数据

writer.close()

运行代码之后,会在项目下创建一个名叫logs的文件夹,打开文件夹会看到刚刚代码生成的事件文件:

在这里插入图片描述

如果要用tensorboard解析该文件并打开,可以在命令行输入:

# 打开名叫logs的文件目录 未指定端口时 端口默认为6006
tensorboard --logdir=logs

# 打开名叫logs的文件目录 指定端口 避免和其他人的端口冲突
tensorboard --logdir=logs --port=6007

点击输出的网址就可以打开了:

在这里插入图片描述

可以看到,TensorBoard已经解析了该事件文件并生成了一个折线图。

如果我们重新运行程序并且没有更改tag,就会出现这种情况:

在这里插入图片描述

所以当我们想重新写入的时候,可以将事件文件删掉重新执行程序

4.3 add_scalar()的使用:观察值

上面的代码示例展示了如何使用该函数,一般我们可以使用add_scalar()来实现每隔多少轮次记录train/val loss等值,绘制折线图等等

4.4 add_image()的使用:观察结果

该函数常用来观察训练的结果,也就是可以将每一(或指定)轮次(步)训练的训练结果(图片)添加到事件文件中,在TensorBoard中查看,示例代码如下:

from torch.utils.tensorboard import SummaryWriter
from PIL import Image
import numpy as np

writer = SummaryWriter("logs")

img_path = "hymenoptera_data/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
# 由于add_image()函数传递的image类型必须是tensor或numpy类型 所以我们进行转换
img_array = np.array(img_PIL)

# 由于此img_array是numpy类型但为(H,W,C)格式的,所以我们还需要加个参数指定一下dataformats
writer.add_image("test", img_array, 1, dataformats='HWC')
# y = x
for i in range(100):
    writer.add_scalar("y = 2x", 2 * i, i)  # scalar_value是纵坐标y轴上的数据 global_step是横坐标x轴上的数据

writer.close()

注意:从PIL到numpy,需要在add_image()中指定shape中每一个数字/维表示的含义

打开TensorBoard查看结果:

在这里插入图片描述

可以看到图片已经插入到里面了

一般我们可以使用这个来查看我们每一轮次训练的结果,做一个对比分析。

5 torchvision中的transforms

5.1 transforms的结构及作用

主要是用于对图片进行一些变换

transforms是一个工具箱,实际上就是一个py文件,里面有很多不同的class文件代表不同的工具,

进入transforms文件后按快捷键Alt+7查看该文件的结构,可以看到有很多的类:

在这里插入图片描述

所以,transforms的作用看下面这个图可以更直观的理解:

在这里插入图片描述

5.2 transforms.ToTensor

通过了解transforms.ToTensor,我们可以解决两个问题:

  • 如何使用transforms
  • 为什么需要Tensor这个数据类型

我们在使用transforms时,选择要使用的工具类,创建其对象实例,直接将图片传入对象中即可(其会自动调用call函数来完成相应的操作),使用transforms的实例代码如下:

from PIL import Image
from torchvision import transforms
import cv2

img_path = "hymenoptera_data/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
img_cv2 = cv2.imread(img_path)

# 实例化ToTensor工具
trans_totensor = transforms.ToTensor()

# PIL to Tensor
img_tensor = trans_totensor(img_PIL)
print(img_tensor)
# numpy to Tensor
img_tensor = trans_totensor(img_cv2)
print(img_tensor)

Tensor数据类型:包装了我们神经网络要用到的一些参数,例如梯度等,将数据转换为Tensor类型,便于后面神经网络的训练,是专门针对GPU来设计的,可以运行在GPU上来加快计算效率

5.3 transforms.Normalize

Normalize归一化,该类可以归一化Tensor类型的图片

transforms.Normalize(mean, std)

输入(channel,height,width)形式的tensor,并输入每个channel对应的均值标准差作为参数,函数会利用这两个参数分别将每层标准化**(使数据均值为0,方差为1)**后输出。

  • mean:(list类型)长度与输入的通道数相同,代表每个通道上所有数值的平均值。
  • std:(list类型)长度与输入的通道数相同,代表每个通道上所有数值的标准差。

计算公式为:

在这里插入图片描述

按理说,我们在设置参数的时候,应该是根据输入数据来计算出每个通道的上数据的平均值和标准差,但是当数据量过大的时候,我们再计算会很困难,这时候就可以将两个参数都设置为0.5并与transforms.ToTensor()一起使用可以使将数据强制缩放到[-1,1]区间上。(标准化只能保证大部分数据在0附近——3σ原则)

原理是tensor的值的范围为[0, 1]的话,将mean和std都设置为0.5,那么上面的公式就变为 2 * input - 1,就会将范围归到[1, -1]之间。代码举例:

from PIL import Image
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs")
img_PIL = Image.open("images/HuGe.jpg")

# ToTensor
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img_PIL)
writer.add_image("img_tensor", img_tensor, 1)
print(img_tensor[0][0][0])

# Normalize(mean, std)
# 由于是三通道,所以每个参数要写三个值
trans_normalize = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
img_norm = trans_normalize(img_tensor)
writer.add_image("img_norm", img_norm, 1)
print(img_norm[0][0][0])

writer.close()

输出控制台可以看到:

在这里插入图片描述

在归一化之前,img_tensor[0][0][0]的值为0.4078,归一化之后变为了-0.1843,这正是 0.4078 * 2 - 1的结果

打开TensorBoard,观察归一化结果:

在这里插入图片描述

5.4 transforms.Resize

用于将输入的PIL图片转换为给出的size

transforms.Resize(size)
  • size:可以是(h,w)也可以是 单个值,当输入为(h,w)时,图片会被Resize为指定的大小,当输入是单个值时,会根据最小的边来等比缩放(将最小的边设置为指定值的同时,另一个边也会跟着等比缩放)
# Resize
trans_resize = transforms.Resize((512, 512))
# 注意 输入的是PIL的Image 输出同样也是PIL的Image
img_resize = trans_resize(img_PIL)
img_resize = trans_totensor(img_resize)
writer.add_image("img_resize", img_resize, 1)

在这里插入图片描述

5.5 transforms.Composes

Composes several transforms together.Compose类将transforms里的工具类都集成到了一起。

一般处理图像我们用Compose把多个步骤整合到一起

transforms.Compose([transforms参数1, transforms参数2, ...])

这里的参数指的各个transforms工具的实例对象,代码举例:

# Compose - Resize - 2
trans_totensor = transforms.ToTensor()
trans_resize_2 = transforms.Resize(512)
trans_compose = transforms.Compose([trans_resize_2, trans_totensor])
img_resize_2 = trans_compose(img_PIL)
writer.add_image("img_resize", img_resize_2, 2)

可以看到,compose的作用就是将多个步骤整合到一起执行,TensorBoard的结果为:

在这里插入图片描述

可以观察到,Resize输入单个值,图片是等比缩放的

注意:Compose的参数列表中是要执行的操作步骤,每一个步骤的输入和输出一定要相互匹配

5.6 transforms.RandomCrop

对PIL图片进行随机裁剪

transforms.RandomCrop(size)
  • size:可以是(h,w)也可以是 单个值,当输入为(h,w)时,图片会被Resize为指定的大小,当输入是单个值时,会默认为(size,size)

代码举例:

# RandomCrop
trans_random = transforms.RandomCrop(512)
trans_compose_2 = transforms.Compose([trans_random, trans_totensor])
img_random = trans_compose_2(img_PIL)
writer.add_image("img_resize", img_random, 3)

TensorBoard的结果为:

在这里插入图片描述

可以看到,RandomCrop将我们的图片随机的裁剪了一块指定大小的区域

5.7 transforms使用总结

  • 在使用transforms中的工具时,首先关注输入和输出类型
  • 多看官方文档
  • 关注方法需要什么参数

6 torchvision中的datasets

6.1 数据集的下载和使用

torchvision中提供了很多经典的数据集供我们使用,可以参考官方文档,里面有各个数据集的介绍:

https://pytorch.org/vision/0.9/datasets.html

以CIFAR10数据集举例,CIFAR10数据集共有60000个样本,每个样本都是一张32*32像素的RGB图像(彩色图像),每个RGB图像又必定分为3个通道(R通道、G通道、B通道)。这60000个样本被分成了50000个训练样本和10000个测试样本。

torchvision.datasets.CIFAR10(root, train, transform, target_transform, download)
  • root:数据集存放的位置
  • train:是否认定为训练集,True将认定为训练集 否则为测试集
  • transform:对数据集中的数据做怎样的transform
  • download:此数据集是否需要下载

如何获得这个数据集,代码示范如下:

import torchvision

# root:设置数据集存放的位置 train:为真则认定为训练集 否则为测试集 download:需不需要下载
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, download=True)

执行代码之后,就会自动下载该数据集,但速度较慢,我们可以复制给出的下载地址到迅雷下载下来。

在这里插入图片描述

如果是在python环境中下载完成后,会自动解压到该压缩包所在位置,如果是迅雷下载的压缩包,那么将压缩包放到dataset文件夹下,重新运行程序,就可以解压了

在这里插入图片描述

接下来,可以对这个数据集进行访问:

# 访问这个dataset的方法和之前自定义dataset的方法一样
# 索引访问返回一个元组 第一个是图片数据 第二个数该图片的类别在类别列表中的索引
img, target = test_set[0]
print(test_set.classes[target])
img.show()

在这里插入图片描述

得到的结果是cat,表明该图片是猫的图片

6.2 数据集结合transforms使用

有时,我们需要对数据集中的数据做transform操作,我们可以在读取数据集的操作和transform的操作结合,也就是设置transform的参数,示例代码如下:

import torchvision
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms

# 创建一个专门对该数据集进行操作的transform
# 也就是将数据集中的图片转换为tensor类型的
dataset_transform = transforms.Compose([
    transforms.ToTensor()
])

# root:设置数据集存放的位置 train:为真则认定为训练集 否则为测试集 download:需不需要下载
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=dataset_transform, download=True)

writer = SummaryWriter("logs_cifar10")
for i in range(10):
    img, target = test_set[i]
    writer.add_image("test_set", img, i)

writer.close()

由于我们希望将图片添加到TensorBoard进行查看,所以我们需要把数据转换为Tensor数据类型,这就涉及到了transform操作中的ToTensor,我们定义好该数据集的transform操作,并赋值给transform参数即可,最后在TensorBoard中可以看到,我们取出了该数据集中的前十张图片:

在这里插入图片描述

注意:每个数据集的加载参数都有可能不同,在使用数据集的时候注意关注官方文档

我们这里看到的是pytorch给我们提供的现成的数据集,一开始我们讲的Dataset是说我们可以自定义数据集,这里现成的数据集只不过是给我们定义好了,都是继承了Dataset类。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侯静川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值