Pytorch学习

B站小土堆教程 小土堆


查看pytorch是否可用 pycharm的一个技巧,在函数的括号里按ctrl+P可以看要写哪些参数。

import torch
print( torch.cuda.is_available() )

如何在pytorch环境中安装jupyter notebook和使用 教程

学习和使用中的两个常用的辅助查询函数
dir():打开package库看看里面有什么,例如dir(torch),看看torch里有什么
help():说明书,例如help(torch.cuda.is_available)

pytorch加载数据

Pytorch学习_第1张图片

Dataset (一般来说Dataset都自己写,然后继承自Dataset)

提供一种方式去获取数据及其label

  • 如何获取每一个数据及其label

  • 告诉我们总共有多少的数据

from torch.utils.data import Dataset

读取一张图片

路径的话用相对路径比较好,因为这样假如换电脑换系统跑代码,绝对路径会发生变化,但只要项目文件不变,相对路径就不会变。另外要注意使用相对路径要保证在同一目录下,如果不是同一目录就要加../,. / 代表目前所在的目录; . . / 代表上一层目录; / 代表根目录。win系统下路径斜杠最好换成/,这样就不会有转义符了。

#读取一张图片的代码
from PIL import Image

img_path = "D:\\Python\Projects_all\\小土堆课程\\hymenoptera_data\\train\\ants\\0013035.jpg" 
                          #win系统记得加两个斜杠\\,表示转义
img = Image.open(img_path) #根据路径读取图片给img变量,可以用python控制台,里面可以显示很多变量信息
print(img.size) #查看图片尺寸,好像并不能看到通道数
img.show()      #打开图片

读取一个文件夹里的所有图片的文件名,并放到一个列表

from PIL import Image
import os

dir_path = "D:\\Python\\Projects_all\\小土堆课程\\hymenoptera_data\\train\\ants" 
            #win系统要加双斜杠,转义符号
img_path_list = os.listdir(dir_path)  #把文件夹里的所有东西的名字变成一个列表
print(img_path_list)  
img0_path = dir_path + "\\" + img_path_list[0]  #第一张图片的路径,手动写路径
img0_path = os.path.join(dir_path,img_path_list[0]) 
            #这个函数可以根据不同系统自动合并路径,两种方法,第二种更好
img0 = Image.open(img0_path)  #读取第一张图片
img0.show()               #打开 查看第一张图片

img0_path = os.path.join(dir_path,img_path_list[0]) #这个函数可以根据不同系统自动合并路径,不同系统斜杠不同

创建数据集(一般来说Dataset都自己写,然后继承自Dataset)

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

class MyData(Dataset):  #创建一个MyData类,继承自Dataset类,这样就继承了Dataset的一部分资源,自己也可以添加一些资源
    def __init__(self,root_dir,label_dir):
        self.root_dir = root_dir
        self.label_dir = label_dir
        self.dir_path = os.path.join(self.root_dir, self.label_dir)
        self.img_path = os.listdir(self.dir_path)  #把文件里的所有东西的名字做成列表

    def __getitem__(self, idx):
        img_name = self.img_path[idx]  #找到对应索引号的图片名字
        img_idx_path = os.path.join(self.root_dir, self.label_dir, img_name) #写出完整路径
        img = Image.open(img_idx_path)  #读取图片
        label = self.label_dir  #图片标签
        return img, label

    def __len__(self):
        return len(self.img_path)

root_dir = "D:\\Python\\Projects_all\\小土堆课程\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)

print(len(ants_dataset)) #输出124
print(len(bees_dataset)) #输出121

img0, label = ants_dataset[0]
img0.show() #显示蚂蚁数据集的第一张图片

train_dataset = ants_dataset + bees_dataset  #两个数据集合并了,
           # 试了下必须用MyData(Dataset)继承Dataset才行,普通类实例化出来的是不行的
print(len(train_dataset)) #输出245

from torch.utils.data import Dataset可以合并数据集

Dataloader (拆分成多个batch)(输出的tensor都是NCHW维度的

为后面的网络提供不同的数据形式。DataLoader是一个可迭代的数据装载器,组合了数据集和采样器,并在给定数据集上提供可迭代对象。可以完成对数据集中多个对象的集成。官方文档

CLASS DataLoader
torch.utils.data.DataLoader(dataset, batch_size=1, shuffle=None, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None, pin_memory=False,
drop_last=False, timeout=0, worker_init_fn=None, multiprocessing_context=None,
generator=None, *, prefetch_factor=2, persistent_workers=False, pin_memory_device='')
先导概念介绍: 转自
Epoch: 所有训练样本都已输入到模型中,称为一个epoch
Iteration: 一批样本(batch_size)输入到模型中,称为一个Iteration
Batchsize: 一批样本的大小, 决定一个epoch有多少个Iteration
常用的主要有以下五个参数:
dataset(数据集):需要提取数据的数据集, Dataset对象
batch_size(批大小):每一次装载样本的个数,int型
shuffle(洗牌):进行新一轮epoch时是否要重新洗牌,Boolean型
num_workers:是否多进程读取机制
drop_last:当样本数不能被batchsize整除时, 是否舍弃最后一批数据
Pytorch学习_第2张图片

官方文档

DataLoader的使用

使用CIFAR10的测试数据集来完成DataLoader的使用。

导入并实例化DataLoader

创建一个dataloader,设置批大小为4,每一个epoch重新洗牌,不进行多进程读取机制,不舍弃不能被整除的批次。

#导入数据集的包
import torchvision.datasets
#导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
#创建测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10",train=False,transform=torchvision.transforms.ToTensor())
#创建一个dataloader,设置批大小为4,每一个epoch重新洗牌,不进行多进程读取机制,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset,batch_size=4,shuffle=True,num_workers=0,drop_last=False)

数据集中数据的读取

由于数据集中的数据已经被我们转换成了tensor型,我们用dataset[0]输出第一张图片,使用shape属性输出tensor类型的大小,target代表图片的标签。

img,target = test_dataset[0]
print(img.shape,target)
输出:torch.Size([3,32,32]) 3  图片有RGB3个通道,大小为32*32,标签target为3。

DataLoader中数据的读取

在dataset中,每一个数据样本元组由一张图片对象img和一个标签target组成;

而dataloader中会分别对一个批次中的图片和标签进行打包,因此dataloader中,每一个对象元组由batchsize张图片对象imgs和batchsize个标签targets组成。(注意取数据的时候并不是按顺序取的,采样器sampler默认是随机采样,也就是说抓取四张图片的时候是随机取的)

  • 对一个batchsize批次中的所有图片对象进行打包,形成一个对象,我们叫它imgs

  • 对一个batchsize批次中所有的标签进行打包,形成一个对象,我们叫它targets

Pytorch学习_第3张图片

我们需要通过for循环来取出loader中的对象,loader中的对象个数=数据集中对象个数/batch_size,故应为10000/4=2500个对象。

#导入数据集的包
import torchvision.datasets
#导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
#创建测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10",train=False,transform=torchvision.transforms.ToTensor())
#创建一个dataloader,设置批大小为4,每一个epoch重新洗牌,不进行多进程读取机制,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset,batch_size=4,shuffle=True,num_workers=0,drop_last=False)
 
#测试数据集中第一张图片对象
img,target = test_dataset[0]
print(img.shape,target)
 
#打印数据集中图片数量
print(len(test_dataset))
 
#loader中对象
for data in test_dataloader:
    imgs,targets = data
    print(imgs.shape)
    print(targets)
 
#dataloader中对象个数
print(len(test_dataloader))

loader中的对象格式:

  • imgs的维度变成了4*3*32*32,即四张图片,每张图片3个通道,每张图片大小为32*32。

  • targets里有4个target,分别是四张图片的标签target,组合到一起了。

Pytorch学习_第4张图片

使用tensorboard可视化效果

修改数据集的batchsize为64,writer中调用的方法为add_images(),因为需要读取的图片有多张

#导入数据集的包
import torchvision.datasets
#导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
#创建测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10",train=False,transform=torchvision.transforms.ToTensor())
#创建一个dataloader,设置批大小为64,每一个epoch重新洗牌,不进行多进程读取机制,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset,batch_size=64,shuffle=True,num_workers=0,drop_last=False)
 
writer = SummaryWriter("log")
 
#loader中对象
step = 0
for data in test_dataloader:
    imgs,targets = data
    writer.add_images("loader",imgs,step)
    step+=1
 
writer.close()

结果如下所示,可以看到一个step中有64张图片。

Pytorch学习_第5张图片

改变shuffle

每一轮epoch之后就是分配完了一次数据,而shuffle决定了是否在新一轮epoch开始时打乱所有图片的属性进行分配。

在代码中epoch就是最外层的循环,假设我们的epoch=2,即需要分配两次数据:

  • shuffle=TRUE代表第一轮循环结束后会打乱数据集中所有图片的顺序重新进行分配。

  • shuffle=FALSE代表第一轮循环结束后不打乱数据集中所有图片的顺序,还是按原顺序进行分配。

#导入数据集的包
import torchvision.datasets
#导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
 
#创建测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10",train=False,transform=torchvision.transforms.ToTensor())
#创建一个dataloader,设置批大小为64,每一个epoch重新洗牌,不进行多进程读取机制,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset,batch_size=64,shuffle=True,num_workers=0,drop_last=True)
 
writer = SummaryWriter("log")
 
#loader中对象
 
for epoch in range(2):
    step = 0
    for data in test_dataloader:
        imgs, targets = data
        writer.add_images("Epoch:{}".format(epoch), imgs, step)
        step += 1
 
writer.close()

可以看到shuffle=True时epoch=0和epoch=1的每一个step中的图片不同了,说明每一轮大循环开始前都在数据集中重新打乱了顺序。

Pytorch学习_第6张图片

import os os库的用法

img_path_list = os.listdir(dir_path) #把文件夹里的所有东西的名字变成一个列表 os.listdir用法,如何找到文件里的jpg

os.listdir(path)返回文件列表的顺序是任意的,sort排序方法

img0_path = os.path.join(dir_path,img_path_list[0]) #这个函数可以根据不同系统自动合并路径

TensorBoard

conda install tensorboard 安装

from torch.utils.tensorboard import SummaryWriter

writer.add_scalar( ) 遇到报错或者不知道参数可以点进去看看函数注释

from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter("logs") # 把对应的事件文件存到logs目录里,如果没有会自动创建

# writer.add_image()
# y = 2x
for i in range(100):
    writer.add_scalar("y=2x", 2*i, i) #第一个参数是图表标题,第二个参数是y轴数值,第三个
                              #参数是步数,相对于x轴。具体参数可以ctrl+左键 点进去看看
writer.close()

代码写好之后先run一下,然后当前目录下就会多一个logs目录,里面存了tensorboard保存的事件文件。

接下来用终端terminal打开这个事件文件,

tensorboard --logdir=D:\Python\Projects_all\小土堆课程\代码\logs

logdir后面是事件文件所在的目录,绝对路径和相对路径都行但是要正确。然后打开http://localhost:6006/ 这个链接就行了。

tensorboard --logdir=D:\Python\Projects_all\小土堆课程\代码\logs --port=6007

用的人太多的话也可以修改端口的。

注意:对于标题一样的图,tensorboard会保存上一次运行的数据,也就是说图上会有上次的数据和这次的数据,全部乱套了。解决方法:一、更换标题,重新运行,刷新tensorboard图表界面,会生成新的图表; 二、删除logs下面所有的事件文件,然后重新运行,并且重新进入终端输入命令。

writer.add_image( ) 遇到报错或者不知道参数可以点进去看看函数注释

可接受的图像格式:img_tensor (torch.Tensor, numpy.array, or string/blobname): Image data

from PIL import Image
img_path = "D:\Python\Projects_all\小土堆课程\hymenoptera_data\\train\\ants\\0013035.jpg"
img = Image.open(img_path)
print(type(img))

可见用PIL这个包来读取图片并不满足tensorboard的格式输入要求。

可以利用Opencv读取图片,获得numpy型图片数据。

这里小土堆直接用numpy对图片格式进行了转化。

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

writer = SummaryWriter("logs") # 把对应的事件文件存到logs目录里,如果没有会自动创建
img_path = "D:\Python\Projects_all\小土堆课程\hymenoptera_data\\train\\ants\\0013035.jpg"
img_PIL = Image.open(img_path)
img_array = np.array(img_PIL)
print(img_array.shape)
# print(np.transpose(2, 0, 1))  # [H, W, C] -> [C, H, W]

writer.add_image("test", img_array, global_step= 1, dataformats="HWC")  # 如果想换一张图片显示,要么就是换标题,
                                                            # 要么不换标题改一下global_step,会有一个滑动的效果
# y = 2x
for i in range(100):
    writer.add_scalar("y=x", 4*i, i) #第一个参数是图表标题,第二个参数是y轴数值,第三个
                              #参数是步数,相对于x轴。具体参数可以ctrl+左键 点进去看看
writer.close()

运行,然后在terminal中输入tensorboard --logdir=D:\Python\Projects_all\小土堆课程\代码\logs。打开链接就能看到图片了。

torchvision

torchvision 中的 transforms

Pytorch学习_第7张图片

from torchvision import transforms

transforms是一个处理图片的工具箱。

ToTensor

用PIL和cv2读取的图片都是HWC格式的(0~255),ToTensor会自动转换成CHW格式(0~1)。

class ToTensor:
"""Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor. """

用PIL的Image来读取图片 读取出来是RGB格式,HWC格式, 0~255

from PIL import Image
from torchvision import transforms

img_path = "../hymenoptera_data/train/ants/0013035.jpg"
img_PIL = Image.open(img_path)
tensor_trans = transforms.ToTensor()
tensor_img = tensor_trans(img_PIL)
print(tensor_img.shape)
输出:torch.Size([3, 512, 768])
Pytorch学习_第8张图片

用opencv的CV2的imread来读取图片 读取出来是BGR格式,HWC格式,0-255

opencv读出来的图片是numpy格式,opencv读取时路径中不可以有中文 opencv读取图片的方法

import cv2
from torchvision import transforms
img_path = "../hymenoptera_data/train/ants/0013035.jpg"
img_cv2 = cv2.imread(img_path)
tensor_trans = transforms.ToTensor()
tensor_img = tensor_trans(img_cv2)
print(tensor_img.shape)
输出:torch.Size([3, 512, 768])

用tensorboard来显示tensor格式的图片

from torch.utils.tensorboard import SummaryWriter
import cv2
from torchvision import transforms
img_path = "../hymenoptera_data/train/ants/0013035.jpg"
img_cv2 = cv2.imread(img_path)
tensor_trans = transforms.ToTensor()
tensor_img = tensor_trans(img_cv2)

writer = SummaryWriter("logs")
writer.add_image("tensor_image", tensor_img)
writer.close()

然后在python console输入命令打开tensorboard查看。

Normalize输入图片归一化

输入必须是tensor,图片tensor的数据范围是0-1。

公式是output[channel] = (input[channel] - mean[channel]) / std[channel]

trans_norm = transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) #均值和方差,图片的三个通道都要归一化
img_norm_PIL = trans_norm(tensor_img_PIL_RGB) #输入必须是tensor类型,注意格式是RGB
img_norm_cv2 = trans_norm(tensor_img_cv2_RGB)
Pytorch学习_第9张图片

第一张图经过了归一化处理,第二张图是原图

Resize修改图片尺寸

输入必须是PIL读取的图片

  • 如果输入的参数是一个序列,即长和宽两个整数,则图像会按该长和宽进行resize。

  • 如果输入的参数是一个整数x,将图片短边缩放至x,长宽比保持不变。

新版的Resize的输入图形类型可以是PIL型或者tensor型

注意:输出图像可能根据其类型不同而不同:当下采样时,PIL图像的插值和张量略有不同,因为PIL应用了抗锯齿。这可能会导致显著的差异在网络的性能中。因此,最好是训练和服务一个具有相同输入的模型类型。转自

trans_resize = transforms.Resize((512, 512))
img_resize = trans_resize(img_PIL_RGB)  # 必须输入PIL读取的图片,输出的也是PIL格式的图片
print(img_PIL_RGB.size)  输出:(768, 512)
print(img_resize.size)   输出:(512, 512)

Compose组合

可以把几个tranforms组合在一起使用,相当于一个组合器,可以对输入图片一次进行多个transforms的操作。

Compose()用法:Compose()中的参数需要是一个列表,Python中,列表的表示形式为[数据1,数据2,...],在Compose中,数据需要是 transforms类型,所以得到,Compose([transforms参数1, transforms参数2,...])。

  • Compose中传入的参数需要是一个列表,列表中的数据类型是transforms型。

  • 参数1的输出类型必须与参数2的输入类型匹配。(因为compose的工作顺序是从左到右的,第一个参数transform介绍之后再进行第二个transform的操作,所以需要前一个的输出和后一个的输入匹配。)

我们结合上面resize的学习进行一个compose的使用,这次resize的参数只输入一个数字,300,即会等比例缩放为短边为300大小的图片。

compose负责把ToTensor和resize组合起来,一步到位实现PIL图形到resize后的tensor图形的转换。

注:
为了测试参数顺序对compose的影响,代码中我写了2个compose,分别调换了ToTensor和resize的顺序,结果完全一致,这是因为ToTensor的输出tensor可以作为resize的输入,而resize的输出PIL也可以作为ToTensor的输入,因此无影响。 转自
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
 
writer = SummaryWriter("logs")
img_path = "image/dog.jpg"
#创建PIL对象
img_PIL = Image.open(img_path)
#创建totensor和resize工具
trans_totensor_tool =transforms.ToTensor()
trans_resize_tool =transforms.Resize(300)
#compose
trans_compose_tool1 = transforms.Compose([trans_totensor_tool,trans_resize_tool])
trans_compose_tool2 = transforms.Compose([trans_resize_tool,trans_totensor_tool])
#图形转换
img_tensor_resized = trans_compose_tool1(img_PIL)
 
writer.add_image("compose",img_tensor_resized)
writer.add_image("compose",img_tensor_resized,1)
 
writer.close()

RandomCrop 随机裁剪

作用:把图像按照随机位置进行裁剪。

参数需要输入想要裁剪成的图片大小。

  • 如果输入的是序列(h,w),会按照该长和宽进行裁剪。

  • 如果输入的是一个整数x,则会按照(x,x)的大小裁剪。

结合Compose进行使用,完成10张大小为512*512的图片的随机裁剪。

from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
 
writer = SummaryWriter("log")
img_path = "image/dog.jpg"
#创建PIL对象
img_PIL = Image.open(img_path)
#创建工具
trans_totensor_tool = transforms.ToTensor()
trans_randomcrop_tool = transforms.RandomCrop(512)
#compose
trans_compose_tool = transforms.Compose([trans_totensor_tool,trans_randomcrop_tool])
for i in range(10):
    img_tensor_ramdomcroped = trans_compose_tool(img_PIL)
    writer.add_image("randomcrop",img_tensor_ramdomcroped,i)
 
writer.close()

总结

  • 关注输入和输出类型

  • 多看官方文档

  • 关注方法需要什么参数

  • 不知道返回值的时候 Print( ) Print(type( )) debug

torchvision中的标准数据集Datasets

torchvision官方提供了一些标准数据集,可以直接在代码里写然后下载使用,官方文档。

Datasets模块提供了需要常用的数据集以及其具体的使用方法,比如下图所示的图像分类中常用的CIFAR10数据集,图像检测中常用的COCO数据集等。

Pytorch学习_第10张图片

以CIFAR10为例

CIFAR-10是一个更接近普适物体的彩色图像的小型数据集。 一共包含10 个类别的RGB 彩色图片:飞机( airplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck )。每个图片的尺寸为32 × 32 ,每个类别有6000个图像,数据集中一共有50000 张训练图片和10000 张测试图片。
Pytorch学习_第11张图片

需要设定的5个参数:

1. root(字符串型):把数据集下载到的位置路径。

2. train(布尔型):是否把该数据集作为训练数据集使用。

  • True: 作为训练数据集创建

  • False:不作为训练数据集,作为测试数据集创建

3. transform:图像需要进行的变换操作,一般使用compose把所需的transforms结合起来。

4. target_transform:对于标签需要做的变换

5. download(布尔型):是否下载数据集。

  • True:把数据集下载到root指定的对应位置;如果数据集以及进行过下载,则不会再一次下载

  • False:不下载数据集

数据集下载

pycharm在线下载(下载速度较快时)

1. 导入torchvision包,然后依次创建训练数据集和测试数据集。注意:训练数据集的train参数要设置为True,测试数据集的train设置为False

import torchvision
#创建训练数据集
train_set = torchvision.datasets.CIFAR10(root="./dataset3",train=True,download=True)
#创建测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset3",train=False,download=True)

2. 点击运行,等待一段时间后显示下载成功

Pytorch学习_第12张图片

3. 观察项目包目录,可以发现自动创建了名为dataset3的文件夹,下载的解压文件和解压好的数据集都在其中。

Pytorch学习_第13张图片

第三方下载

如果在pycharm中下载速度很慢的话,可以找到pycharm所用的下载链接,然后自己使用迅雷等下载软件进行快速下载。

如何找到下载链接?

  1. 把鼠标移动到想要下载的数据集名称上,然后Ctrl+C,进入该数据集的帮助文档。

Pytorch学习_第14张图片

2. 可以看到对应的下载文件名和下载链接。

Pytorch学习_第15张图片

3. 使用迅雷或者浏览器下载,然后把下载过后的压缩文件按照root中定义的路径创建文件夹,然后把文件放入文件夹中,注意,自己创建的文件夹一定要和root中定义的文件夹姓名相同才行,否则后期扫描不到该数据集

4. 运行上面在线下载中定义的语句,可以发现程序不会再次下载数据集文件,而是会帮你解压好数据集。

数据集的下载总结

无论是否需要在线下载数据集,都推荐把download参数值设为True。
因为程序可以帮你自动完成下载解压工作,就算自己下载过文件,也可以提供解压功能,因此更加方便。

CIFAR10的具体使用

数据集对象的显示(PIL型)

import torchvision
#创建训练数据集
train_set = torchvision.datasets.CIFAR10(root="./dataset3",train=True,download=True)
#创建测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset3",train=False,download=True)
 
#1. 查看数据集的图片
#输出所有类别
print(test_set.classes)
#输出数据集第一张图片的类型
print(test_set[0])
#输出图片的PIL型格式和标签
img,label = test_set[0]
print(label,test_set.classes[label])
img.show()
1. 数据集所有类别的查看
图片有十个类,对应的类别名称存储在dataset.classes列表中。
2. 数据集中单个具体对象的查看
想要输出数据集中具体的某一张图片,使用下标调用方式dataset[x]即可显示第x+1张图片;输出的对象类型为一个元组, 里面第一项是PIL类型的图片,第二项是图片的标签。
3. 数据集中图片对象和标签的定义
可以使用 img,label = dataset[x] 的方式接收对象中的图片和label,然后可以用print进行对label的输出,也可以用 dataset. classes[label]的格式进行对该类别名称的显示。
4. 数据集中图片的可视化
使用img.show()方法进行图片的可视化显示

把数据集中的图片对象转换为tensor型

转换所需transform的定义

因为需要完成数据集中所有图片类型从PIL到tensor的转换,我们需要用到transforms工具,也需要设定数据集中的transform参数

我们在数据集定义的语句之前定义我们需要的transform。在这里我们只需要一个ToTensor即可。

下面代码给出使用compose定义transform和不使用compose的两个版本,都可以完成成功运行。

  • 使用compose:

import torchvision
#定义transforms
dataset_transform = torchvision.transforms.Compose([
    #定义totensor
    torchvision.transforms.ToTensor()
])
#创建训练数据集
train_set = torchvision.datasets.CIFAR10(root="./dataset3",train=True,transform=dataset_transform,download=True)
#创建测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset3",train=False,transform=dataset_transform,download=True)
  • 不使用compose:

import torchvision
#定义transforms
from torch.utils.tensorboard import SummaryWriter
 
trans_totensor_tool = torchvision.transforms.ToTensor()
#创建训练数据集
train_set = torchvision.datasets.CIFAR10(root="./dataset3",train=True,transform=trans_totensor_tool,download=True)
#创建测试数据集
test_set = torchvision.datasets.CIFAR10(root="./dataset3",train=False,transform=trans_totensor_tool,download=True)

torchvision中现有网络模型的使用与修改

VGG地址 可以分类1000个类别,因为ImageNet就是1000类。

Pytorch学习_第16张图片

pretrained为True指权重预训练好了已经;progress为True开启下载进度条。

因为ImageNet太大了,所以用CIFAR10数据集来演示,但是CIFAR10只有10个类别,所以需要对VGG网络结构进行修改。两种方法:

  • 在VGG的全连接层最后一层把1000改成10

  • 在VGG的全连接层的最后一层后面再加一层,输出是10

VGG的模型代码中分为feature和classifier。

import torchvision
from torch import nn

vgg16_flase = torchvision.models.vgg16(pretrained = False) # 不下载预训练权重
vgg16_ture = torchvision.models.vgg16(pretrained = True) # 下载预训练权重,运行就会自动下载权重了
print(vgg16_ture) # 看看模型结构

train_data = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)

#添加新的一层
vgg16_ture.add_module('add_linear', nn.Linear(1000, 10)) # 在提特征层和分类层外面加
vgg16_ture.classifier.add_module('add_linear', nn.Linear(1000, 10)) # 加到分类层里面
print(vgg16_ture)

#修改
vgg16_flase.classifier[6] = nn.Linear(4096, 10) # 把模型打印出来再修改比较直观
print(vgg16_flase)

RGB格式和BGR格式之间的互相转换

cv2.cvtColor(img_PIL_RGB_np, cv2.COLOR_RGB2BGR) 转换函数

opencv读出来的图片是numpy格式,opencv读取时路径中不可以有中文

from PIL import Image
import cv2
from torchvision import transforms
import numpy as np

img_path = "../hymenoptera_data/train/ants/0013035.jpg"
img_PIL_RGB = Image.open(img_path)  # RGB 格式 HWC 0~255
img_cv2_BGR = cv2.imread(img_path)  # BGR 格式 HWC 0~255

# 如果想用cv2显示图片,图片必须是BGR格式的才能正常显示
img_PIL_RGB_np = np.array(img_PIL_RGB)  # 512X768x3 HWC  变成numpy格式
img_PIL_BGR_np = cv2.cvtColor(img_PIL_RGB_np, cv2.COLOR_RGB2BGR)  # 把RGB转换成BGR
cv2.imshow("img_PIL_BGR", img_PIL_BGR_np)  # cv2显示的图片必须是BGR格式的才能正常显示
cv2.imshow("img_cv2_RGB", img_PIL_RGB_np)  # RGB的是显示不正常的
cv2.imshow("img_cv2_BGR", img_cv2_BGR)
cv2.waitKey(0)  # 一直显示图片,直到按下任意键才关掉继续执行程序。里面的参数是显示的时间,单位ms

tensor_trans = transforms.ToTensor() # 会自动把HWC变成CHW,把0-255变成0-1,但是不会改变RGB和BGR格式
tensor_img_PIL_RGB = tensor_trans(img_PIL_RGB)

img_cv2_RGB = cv2.cvtColor(img_cv2_BGR, cv2.COLOR_BGR2RGB)  # BGR 转换成 RGB  HWC

def bgr2rgb(image):  # 自己写的BGR转换成RGB格式的函数,用numpy切片索引实现
    # 输入图片是HWC
    b_channel = image[:, :, 0].copy()  # copy表述b_channel现在和image没有关系了,image怎么变都影响不了b_channel了
    r_channel = image[:, :, 2].copy()
    image[:, :, 0] = r_channel   # 不copy的话这里image变了,b_channel会跟着改变
    image[:, :, 2] = b_channel
    return image

img_cv2_RGB = bgr2rgb(img_cv2_BGR)  # 用自己写的函数来BGR 转换成 RGB

tensor_img_cv2_RGB = tensor_trans(img_cv2_RGB)
print(tensor_img_PIL_RGB)
print(tensor_img_cv2_RGB)

PNG格式的图片需要去掉透明度通道

PNG图片HWC的C有4个通道,其中一个是透明度通道。

import numpy as np
from PIL import Image
import cv2

img_PIL = Image.open("0324204803.png") # 用PIL打开有4个通道
img_np = np.array(img_PIL)
print(img_np.shape)   # 输出(275, 115, 4)

img_cv = cv2.imread("0324204803.png") # 用cv2不加任何参数打开会变成3通道,(275, 115, 3)
img_cv = cv2.imread("0324204803.png",cv2.IMREAD_UNCHANGED) #用cv2加参数打开是4通道
cv2.imshow("img_cv", img_cv) # 我感觉cv2打开应该是BGR,但是显示是正常的
cv2.waitKey(0)
print(img_cv.shape) # 输出(275, 115, 4)

PNG格式图片4通道变RGB3通道方法

img_PIL = Image.open("0324204803.png") # 4通道(275, 115, 4)
img_PIL = img_PIL.convert('RGB')
img_np = np.array(img_PIL)
print(img_np.shape) # 输出(275, 115, 3)

如果原来就是RGB3通道,那加上这个.convert('RGB')也没事。

或者直接用opencv不加任何参数,读进来会变成3通道,但不一定是RGB

img_cv = cv2.imread("0324204803.png") # 用cv2不加任何参数打开会变成3通道,(275, 115, 3)

神经网络基本骨架 torch.nn.Module 官方文档

我们自己定义的神经网络需要继承nn.Module类,需要重写以下两个方法:

  1. init方法:初始化

  1. forward方法:前向传播

在官方文档给出的示例中, 在init方法中进行了卷积操作,在forward方法中进行了ReLu非线性处理操作,代码如下所示。

  • 对输入的x进行第一次卷积,再进行第一次非线性操作;

  • 再第二次进行卷积,然后第二次非线性操作。

import torch.nn as nn
import torch.nn.functional as F
 
class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5) # 卷积核
        self.conv2 = nn.Conv2d(20, 20, 5)
 
    def forward(self, x):
        x = F.relu(self.conv1(x))
        return F.relu(self.conv2(x))
Pytorch学习_第17张图片

自定义搭建神经网络

  1. 自定义名为Ysy的神经网络,继承自nn.Module

  1. 重写init和forward两个方法,在forward中定义该神经网络的规则,即输入+1为输出

  1. 实例化神经网络

  1. 把tensor类型的值x放入神经网络中进行输出

  1. 成功输出结果为 x+1

import torch
from torch import nn

class Ysy(nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
 
    def forward(self,input):
        output = input + 1;
        return output
 
# 创建神经网络
ysy = Ysy()
# 输入的值为x,tensor型,数值为1
x = torch.tensor(1.0)
# 输出为output
output = ysy(x)
print(output)

这里在计算的时候并没有调用forward方法,学长说就理解成nn.Module里面的__call__里面调用了forward就行。

神经网络之卷积操作

两种2d卷积的实现方式

  • torch.nn Conv2d是类

CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
  • torch.nn.functional conv2d是函数

torch.nn.functional.conv2d(input, weight, bias=None, stride=1, padding=0, dilation=1, groups=1) → Tensor
import torch.nn.functional as F

torch.nn.functional.conv2d方法介绍

使用该方法需要引入的参数

torch.nn.functional.conv2d( input, weight, bias=None, stride=1, padding=0,
dilation=1, groups=1) → Tensor
Pytorch学习_第18张图片

https://pytorch.org/docs/stable/generated/torch.nn.functional.conv2d.html#torch.nn.functional.conv2d

weight就是卷积核。

stride步长参数可以是元组,一个是H方向的步长,一个是W方向的步长。

padding可以是一个元组,纵向和横向padding的参数。

关于对input和weight的shape详解

方法中对input和weight要求的shape都需要是4维的。

input的shape要求:
  • minibatch:batch中的样例个数

  • in_channels:每个样例数据的通道数

  • iH:每个样例的高(行数)

  • iW:每个样例的宽(列数)

weight的shape要求:
  • out_channels:卷积核的个数

  • in_channels/groups:每个卷积核的通道数

  • kH:每个卷积核的高(行数)

  • kW:每个卷积核的宽(列数)

正常定义的tensor型shape为二维,即只有长和宽,因此 需要使用reshape方法进行尺寸重定义。
import torch
import torch.nn.functional as F

# 输入
input = torch.tensor([[1,2,0,3,1],
                      [0,1,2,3,1],
                      [1,2,1,0,0],
                      [5,2,3,1,1],
                      [2,1,0,1,1]])
# 卷积核
kernel = torch.tensor([[1,2,1],
                       [0,1,0],
                       [2,1,0]])
print(input.shape)
print(kernel.shape)
 
# 重新定义尺寸,把尺寸改成四个数,1个batchsize,1个通道,长和宽和之前一样
input = torch.reshape(input,(1,1,5,5))
kernel = torch.reshape(kernel,(1,1,3,3))
 
print(input.shape)
print(kernel.shape)
 
#stride=1或2时的输出
output1 = F.conv2d(input,kernel,stride=1)
print(output1)
output2 = F.conv2d(input,kernel,stride=2)
print(output2)
 
# padding=1或2时的
output3 = F.conv2d(input,kernel,stride=1,padding=1)
print(output3)
output4 = F.conv2d(input,kernel,stride=1,padding=2)
print(output4)

torch.nn.Conv2d

CLASS torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
Pytorch学习_第19张图片

参数介绍

in_channels ( int ) – Number of channels in the input image,输入图片的通道数
out_channels ( int ) – Number of channels produced by the convolution,输出图片的通道数, 代表卷积核的个数,使用n个卷积核输出的特征矩阵深度即channel就是n
kernel_size ( int or tuple ) – Size of the convolving kernel,卷积核的大小
e.g. if kernel size = 3, 则卷积核的大小是3*3
stride ( int or tuple , optional) – Stride of the convolution. Default: 1,步径大小
padding ( int , tuple or str , optional) – Padding added to all four sides of the input. Default: 0
padding_mode ( str , optional) – 'zeros', 'reflect', 'replicate' or 'circular'. Default: 'zeros'
dilation ( int or tuple , optional) – Spacing between kernel elements. Default: 1
groups ( int , optional) – Number of blocked connections from input channels to output channels. Default: 1
bias ( bool , optional) – If True, adds a learnable bias to the output. Default: True
Pytorch学习_第20张图片

输出的尺寸大小

dilation:空洞卷积,进行卷积操作时会隔n个取一个。

Pytorch学习_第21张图片
import torch
import torchvision
from torch.nn import Conv2d
from torch.utils.data import DataLoader
# 数据集下载
from torch.utils.tensorboard import SummaryWriter
 
dataset = torchvision.datasets.CIFAR10(root=".\CIFAR10", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)
# 数据加载器
dataloader = DataLoader(dataset, batch_size=64)
 
class Ysy(torch.nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
        # 卷积层
        self.conv1 = Conv2d(in_channels=3, out_channels=6, kernel_size=3, stride=1, padding=0)
 
    def forward(self, x):
        x = self.conv1(x)
        return x
 
ysy = Ysy()

writer = SummaryWriter("logs")
step = 0
for data in dataloader:
    imgs, labels = data
    # 卷积操作
    output = ysy(imgs) # 卷积核权重是随机生成的
    print(imgs.shape)
    print(output.shape)
    writer.add_images("input", imgs, step)
 
    output = torch.reshape(output, (-1, 3, 30, 30)) #输出有6个通道但是为了可视化以下强行reshape成3个通道,没什么卵用
    writer.add_images("output", output, step)
    step = step + 1

writer.close()

ReflectionPad2d--利用输入边界的反射来padding输入张量 链接

最大池化(下采样)

MaxPool2d是下采样;MaxUnpool是上采样

CLASS torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
Pytorch学习_第22张图片

stride的默认大小为池化核的大小

ceil_mode:

Pytorch学习_第23张图片

遇到这种不足9个数的情况时是否要对这6个数进行池化,默认False就不管了,True就要池化这6个数。

MaxPool2d不支持输入的tensor的数据类型是long,会报错。

Pytorch学习_第24张图片

最大池化的作用和目的

作用:最大限度的保留图片特征,同时 减少数据量,加速训练速度
import torch
from torch.nn import MaxPool2d
#输入的矩阵 
input = torch.tensor([
    [1,2,0,3,1],
    [0,1,2,3,1],
    [1,2,1,0,0],
    [5,2,3,1,1],
    [2,1,0,1,1]
],dtype=torch.float32)
 
input = torch.reshape(input,(-1,1,5,5))
print(input.shape)
 
class Ysy(torch.nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
        # 设置池化
        self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=False)
 
    def forward(self,input):
        output = self.maxpool1(input)
        return output
 
ysy = Ysy()
output = ysy(input)
print(output)
import torch
import torchvision.datasets
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

dataset = torchvision.datasets.CIFAR10(root=".\CIFAR10",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=64)

class Ysy(torch.nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
        # 设置池化
        self.maxpool1 = MaxPool2d(kernel_size=3,ceil_mode=False)
 
    def forward(self,input):
        output = self.maxpool1(input)
        return output
 
writer = SummaryWriter("logs")
step = 0
ysy = Ysy()
 
for data in dataloader:
    imgs,labels=data
    writer.add_images("inputs",imgs,step)
    output = ysy(imgs)
    writer.add_images("output",output,step)
    step+=1
 
writer.close()

torch.cat([feature_B,feature_D],1)

一般卷积网络里的tensor的shape都是(N,C,H,W),所以concatenate的时候想拼接通道就应该是dim=1,在C的维度进行拼接。

非线性激活

作用:非线性变换的主要目的就是给网中加入一些非线性特征,非线性越多才能训练出符合各种特征的模型。

ReLu

CLASStorch.nn.ReLU(inplace=False)
Pytorch学习_第25张图片

inplace代表是否替换/保留原输入的意思

  • inplace = True,代表确定替换,即不保留非线性激活之前的值,会把输入值直接替换为激活后的值。

  • inplace = False(默认),代表不替换,即保留非线性激活之前的值,会把输入值保留下来,再另存一个输出值。

import torch
from torch.nn import ReLU
 
input = torch.tensor([
    [1,-0.5],
    [-1,3]])
 
input= torch.reshape(input,(-1,1,2,2))
 
class Ysy(torch.nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
        # inplace默认为FALSE,可以不进行设置
        self.relu = ReLU()
 
    def forward(self,input):
        output = self.relu(input)
        return output
 
ysy = Ysy()
output = ysy(input)
print(output)

Sigmoid

CLASStorch.nn.Sigmoid(*args, **kwargs)
import torch
import torchvision.datasets
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

dataset = torchvision.datasets.CIFAR10(root=".\CIFAR10",train=True,transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=64)

class Ysy(torch.nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
        # inplace默认为FALSE,可以不进行设置
        self.relu = ReLU()
        #设置sigmoid激活
        self.sigmoid1 = Sigmoid()
 
    def forward(self,input):
        output = self.sigmoid1(input)
        return output
 
ysy = Ysy()
 
writer = SummaryWriter("logs")
step = 0
for data in dataloader:
    imgs,labels = data
    writer.add_images("inputs",imgs,step)
    outputs = ysy(imgs)
    writer.add_images("outputs",outputs,step)
    step+=1
 
writer.close()

Batch-norm 官方文档

CLASS torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)
Pytorch学习_第26张图片

nn.BatchNorm2d(channel)

BN层里的参数也是可以学习的。

全连接层

Pytorch学习_第27张图片

基于公式y=wx+b计算,w为权重,x为输入,b为偏置值。

参数:
in_features,输入特征 ( int ) – size of each input sample,输入的tensor大小
out_features,输出特征 ( int ) – size of each output sample,输出的tensor大小
bias ( bool ) – If set to False, the layer will not learn an additive bias. Default: True
import torch
import torchvision.datasets
from torch.nn import Linear
from torch.utils.data import DataLoader
 
dataset = torchvision.datasets.CIFAR10(root="./CIFAR10", train=False, transform=torchvision.transforms.ToTensor(),
                                       download=True)
dataloader = DataLoader(dataset, batch_size=64)
 
class Ysy(torch.nn.Module):
    def __init__(self):
        super(Ysy, self).__init__()
        self.linear1 = Linear(196608, 10)
 
    def forward(self, input):
        output = self.linear1(input)
        return output
 
ysy = Ysy()
for data in dataloader:
    imgs, lables = data
    print(imgs.shape)
    input = torch.flatten(imgs) 或者 out = torch.reshape(imgs, (1, 1, 1, -1))
    print(input.shape)  # 这里的展平有点问题,不应该把batch的所有图片展平到一起,应该分开展平
    output = ysy(input)
    print(output.shape)

展平特征用这个更多CLASS torch.nn.Flatten(start_dim=1, end_dim=- 1)

input = torch.randn(32, 1, 5, 5)
# With default parameters
m = nn.Flatten()
output = m(input)
output.size()  # torch.Size([32, 25])
# With non-default parameters
m = nn.Flatten(0, 2)
output = m(input)
output.size()  # torch.Size([160, 5])

Sequential的使用

构建一个序列化的container,可以把想要在神经网络中添加的操作都放进去,按顺序进行执行。

Example

把卷积、非线性激活、卷积、非线性激活使用sequantial进行组合,一起放在构建的model中。

model = nn.Sequential(
          nn.Conv2d(1,20,5),
          nn.ReLU(),
          nn.Conv2d(20,64,5),
          nn.ReLU()
        )
 
# Using Sequential with OrderedDict. This is functionally the
# same as the above code
model = nn.Sequential(OrderedDict([
          ('conv1', nn.Conv2d(1,20,5)),
          ('relu1', nn.ReLU()),
          ('conv2', nn.Conv2d(20,64,5)),
          ('relu2', nn.ReLU())
        ]))

实战神经网络搭建以及sequential的使用

Pytorch学习_第28张图片

不使用sequential

import torch.nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear
 
class Maweiyi(torch.nn.Module):
    def __init__(self):
        super(Maweiyi, self).__init__()
        self.conv1 = Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2)
        self.maxpool1 = MaxPool2d(kernel_size=2)
        self.conv2 = Conv2d(in_channels=32, out_channels=32, kernel_size=5, padding=2)
        self.maxpool2 = MaxPool2d(kernel_size=2)
        self.conv3 = Conv2d(in_channels=32, out_channels=64, kernel_size=5, padding=2)
        self.maxpool3 = MaxPool2d(kernel_size=2)
        self.flatten = Flatten()
        self.linear1 = Linear(in_features=1024, out_features=64)
        self.linear2 = Linear(in_features=64, out_features=10)
 
    def forward(self, x):
        x = self.conv1(x)
        x = self.maxpool1(x)
        x = self.conv2(x)
        x = self.maxpool2(x)
        x = self.conv3(x)
        x = self.maxpool3(x)
        x = self.linear1(x)
        x = self.linear2(x)
        return x

使用sequential

class Maweiyi(torch.nn.Module):
    def __init__(self):
        super(Maweiyi, self).__init__()
        self.model1 = Sequential(
            Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(in_channels=32, out_channels=32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(in_channels=32, out_channels=64, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Flatten(),
            Linear(in_features=1024, out_features=64),
            Linear(in_features=64, out_features=10)
        )
 
    def forward(self, x):
         x = self.model1(x)
         return x

tensorboard SummaryWriter中的add_gragh可视化神经网络模型

import torch.nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter
 
class Maweiyi(torch.nn.Module):
    def __init__(self):
        super(Maweiyi, self).__init__()
        self.model1 = Sequential(
            Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(in_channels=32, out_channels=32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(in_channels=32, out_channels=64, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Flatten(),
            Linear(in_features=1024, out_features=64),
            Linear(in_features=64, out_features=10)
        )
 
    def forward(self, x):
         x = self.model1(x)
         return x

maweiyi = Maweiyi()
print(maweiyi)
 
input = torch.ones((64,3,32,32))
output = maweiyi(input)
print(output.shape)
 
writer = SummaryWriter("logs")
writer.add_graph(maweiyi,input)
writer.close()
Pytorch学习_第29张图片

损失函数

L1loss nn.L1Loss 绝对值误差

CLASS torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
Pytorch学习_第30张图片
import torch
from torch.nn import L1Loss
 
inputs = torch.tensor([1,2,3],dtype=torch.float32)
targets = torch.tensor([1,2,5],dtype=torch.float32)
inputs = torch.reshape(inputs,(1,1,1,3))
targets = torch.reshape(targets,(1,1,1,3))
loss2 = L1Loss(reduction="sum")
result2 = loss2(inputs,targets)
print(result2)

MSE损失函数(均方误差)MSELoss

Pytorch学习_第31张图片
CLASS torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
import torch
from torch.nn import L1Loss, MSELoss
 
inputs = torch.tensor([1,2,3],dtype=torch.float32)
targets = torch.tensor([1,2,5],dtype=torch.float32)
 
inputs = torch.reshape(inputs,(1,1,1,3))
targets = torch.reshape(targets,(1,1,1,3))
 
loss_mse1 = MSELoss()
result1 = loss_mse1(inputs,targets)
print(result1)

使用交叉熵损失函数CrossEntropyLoss

注意这个交叉熵函数里面包括了softmax最后的归一化处理,所以输入给它的数得是没经过softmax归一化的数据。

CLASS torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='mean', label_smoothing=0.0)
Pytorch学习_第32张图片

这张图用softmax的交叉熵来演示,至于为什么写的这么复杂,是因为这里写的[0.1,0.2,0.3]并不是softmax归一化之后的概率而是输入给softmax的数(得分),因此损失函数也要加一个softmax归一化,softmax的损失函数就是-log y_hat。

Pytorch学习_第33张图片

参数说明

Pytorch学习_第34张图片

输入输出说明

import torch.nn
from torch import nn
import torchvision.datasets
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
 
dataset = torchvision.datasets.CIFAR10(root="./CIFAR10",train=False,transform=torchvision.transforms.ToTensor(),download=True)
dataloader = DataLoader(dataset,batch_size=1)

class Maweiyi(torch.nn.Module):
    def __init__(self):
        super(Maweiyi, self).__init__()
        self.model1 = Sequential(
            Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(in_channels=32, out_channels=32, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Conv2d(in_channels=32, out_channels=64, kernel_size=5, padding=2),
            MaxPool2d(kernel_size=2),
            Flatten(),
            Linear(in_features=1024, out_features=64),
            Linear(in_features=64, out_features=10)
        )
 
    def forward(self, x):
         x = self.model1(x)
         return x
 
maweiyi = Maweiyi()
# 使用交叉熵损失函数
loss_cross = nn.CrossEntropyLoss()
 
for data in dataloader:
    imgs,labels = data
    outputs = maweiyi(imgs)
    results = loss_cross(outputs,labels)
    results.backward()  # 反向传播

优化器,梯度下降

import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential, ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

dataset = torchvision.datasets.CIFAR10(root="dataset", train=False, transform=torchvision.transforms.ToTensor()
                                       ,download=False)
dataloader = DataLoader(dataset, batch_size=1)

class ZYJ(nn.Module):
    def __init__(self):
        super(ZYJ, self).__init__()
        self.model = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            ReLU(),
            Conv2d(32, 32, 5, padding=2),
            Sigmoid(),
            MaxPool2d(2),
            Conv2d(32, 64, 5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024, 64),
            Linear(64, 10)
        )
    def forward(self, input):
        output = self.model(input)
        return output

zyj = ZYJ()
loss = nn.CrossEntropyLoss()
optim = torch.optim.SGD(zyj.parameters(), lr=0.01)
for epoch in range(50):
    running_loss = 0.0

    for data in dataloader:
        imgs, targets = data
        output = zyj(imgs)  # 边计算,边创建计算图
        result_loss = loss(output, targets) #边计算,边创建计算图
        optim.zero_grad() # 梯度清零,pytorch默认梯度会累加,清除上一次的梯度
        result_loss.backward()  # 获得每个参数的梯度,后续用优化器进行优化参数
        optim.step() # 梯度下降,更新参数,默认会删除计算图
        running_loss = running_loss + result_loss # 把一个batch里的所有小块的loss加起来了
    print(running_loss)

Pytorch的动态图、自动求导

Pytorch的自动求导系统要注意:梯度不自动清零: 就是每一次反向传播,梯度都会叠加上去。所以迭代的时候要清零。

模型的保存与加载

import torch
import torchvision
from torch import nn

vgg16 = torchvision.models.vgg16(pretraned = False)

#保存方式1,保存网络模型和权重
torch.save(vgg16, "vgg16_method1.pth") # 第二个参数是保存路径

#保存方式1,保存网络模型和权重→对应的模型加载
model = torch.load("vgg16_method1.pth")

#保存方式2,只保存权重,不保存模型 (官方推荐)
torch.save(vgg16.state_dict(), "vgg16_method2.pth")

#保存方式2,只保存权重,不保存模型 (官方推荐)→对应的模型加载
vgg16 = torchvision.models.vgg16(pretrained = False)
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))
print(vgg16)

#保存方式1  注意陷阱! 在加载模型的时候,需要有模型定义!
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model1 = nn.Sequential(
            nn.Linear(64, 10)
        )
    def forward(self, x):
        x = self.model1(x)
        return x

tudui = Tudui()
torch.save(tudui, "model/tudui_method.pth")  #按照保存方式1,保存模型和权重

# 加载模型
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model1 = nn.Sequential(
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model1(x)
        return x

model = torch.load("model/tudui_method.pth") # 可以省略tudui = Tudui()实例化这一步,但是模型定义不能缺
print(model)
#不过一般都不会有这种问题,因为我们一般把模型放在一个单独的文件里 from model_save import *

保存和加载checkpoint

保存的checkpoint里面除了保存了权重还可能保存了优化器参数,epoch,loss等等参数。为啥我保存的权重文件那么大? 如何保存与加载checkpoints

保存checkpoints

EPOCH = 5
PATH = "model.pt"
LOSS = 0.4

torch.save({
            'epoch': EPOCH,
            'model_state_dict': net.state_dict(),
            'optimizer_state_dict': optimizer.state_dict(),
            'loss': LOSS,
            }, PATH)

加载checkpoints

model = Net()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

checkpoint = torch.load(PATH)
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
 
model.eval()
# - or -
model.train()

pytorch模型的保存和加载、checkpoint pytorch实现加载保存查看checkpoint文件

完整的模型小项目

Pytorch学习_第35张图片

用上面这个简单模型给CIFAR10数据集分类,10个类别。

通常模型都单独放在一个python文件里一般是model

model.py

import torch
from torch import nn

#搭建神经网络
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            nn.Conv2d(3, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.MaxPool2d(2),
            nn.Flatten(),
            nn.Linear(1024, 64),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x

if __name__ == '__main__': # 测试一下模型对不对
     tudui = Tudui()
     input = torch.ones((64, 3, 32, 32))
     output = tudui(input)
     print(output.shape) # shape和size功能一样

model.py和train.py必须在同一目录下

train.py (CPU训练)

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import *

#准备数据集
train_data = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), download=True)

#数据集长度
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集长度:{}".format(train_data_size))
print("测试数据集长度:{}".format(test_data_size))

#加载数据集(Dataloader)
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

#创建网络模型
tudui = Tudui()

#损失函数
loss_fn = nn.CrossEntropyLoss()

#优化器
#learning_rate = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

#参数
total_train_step = 0
total_test_step = 0
epoch = 10

#添加tensorbooard
writer = SummaryWriter("logs/trainlogs")

for i in range(epoch):
    print("-----第{}轮训练-----".format(i+1))

    #训练开始
    tudui.train() # 让模型进入训练状态
    for data in train_dataloader:
        imgs, targets = data
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        #优化器调优
        optimizer.zero_grad() #梯度清零
        loss.backward() # 求梯度
        optimizer.step() # 梯度下降

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{},Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    #一轮训练过后我要看看模型训练的效果如何,所以要在验证集上评估一下
    tudui.eval() # 让模型进入验证评估状态
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad(): # 去掉梯度,只有前向推理
        for data in test_dataloader:
            imgs, targets = data
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets) #看一下验证集上的loss
            total_test_loss = total_test_loss + loss.item()#把所有验证集batch的loss加起来
            accuracy = (outputs.argmax(1) == targets).sum() #求准确率
            total_accuracy = total_accuracy + accuracy #整体验证集的准确率,但是我觉得应该不能是直接累加

    print("整体验证集的loss:{}".format(total_test_loss))
    print("验证集正确率: {}".format(total_accuracy / test_data_size))
    total_test_step = total_test_step + 1
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)

    torch.save(tudui, "model/tudui_{}.pth".format(i))
    # torch.save(tudui.state_dict(), "tudui_{}.pth".format(i))
writer.close()

一维或零维张量.item( )方法是,取一个张量里面的具体元素值并返回该值,可以将一个零维张量转换成int型或者float型,在计算loss,accuracy时常用到。

张量.argmax(1)的用法,返回最大值对应的序号

import torch

output = torch.tensor([[0.2, 0.8],
                       [0.4,0.6]])
print(output.argmax(0)) # tensor([1, 0])竖着找最大值的序号
print(output.argmax(1)) # tensor([1, 1])横着找最大值的序号

tudui.train() # 让模型进入训练状态

对BN和dropout有作用,其他的好像没什么用

Pytorch学习_第36张图片

tudui.eval() # 让模型进入验证评估状态

对BN和dropout有作用,其他的好像没什么用

Pytorch学习_第37张图片

利用GPU来训练

方法一:

在这些东西后面加.cuda( )

网络模型 tudui = tudui.cuda( ) tudui是模型

数据(输入数据,标签) imgs = imgs.cuda() targets = targets.cuda() 是在训练的for循环里加

损失函数 loss_fn = loss_fn.cuda()

师兄说只要是tensor张量和模型就可以加.cuda

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

from model import *

#准备数据集
train_data = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), download=True)

#数据集长度
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集长度:{}".format(train_data_size))
print("测试数据集长度:{}".format(test_data_size))

#加载数据集(Dataloader)
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

#创建网络模型
tudui = Tudui()
if torch.cuda.is_available():#1、网络模型后面加.cuda
    tudui = tudui.cuda()

#损失函数
loss_fn = nn.CrossEntropyLoss()
if torch.cuda.is_available():#2、损失函数后面加.cuda
    loss_fn = loss_fn.cuda()

#优化器
#learning_rate = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

#参数
total_train_step = 0
total_test_step = 0
epoch = 10

#添加tensorbooard
writer = SummaryWriter("logs/trainlogs")

for i in range(epoch):
    print("-----第{}轮训练-----".format(i+1))

    #训练开始
    tudui.train()#模型进入训练状态
    for data in train_dataloader:
        imgs, targets = data
        if torch.cuda.is_available():#3、数据后面加.cuda
            imgs = imgs.cuda()
            targets = targets.cuda()
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        #优化器调优
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{},Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    #验证集评估
    tudui.eval()#模型进入评估状态
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            if torch.cuda.is_available():#3、数据后面加.cuda
                imgs = imgs.cuda()
                targets = targets.cuda()
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("测试集loss:{}".format(total_test_loss))
    print("测试集正确率: {}".format(total_accuracy / test_data_size))
    total_test_step = total_test_step + 1
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)

    torch.save(tudui, "model/tudui_{}.pth".format(i))

writer.close()

方法二:(常用,可以选择显卡)

#定义训练的设备

device = torch.device("cuda")

device = torch.device("cuda:0") # 对于单显卡来说这两句话是没有区别的

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

tudui = tudui.to(device)

loss_fn = loss_fn.to(device)

imgs = imgs.to(device)

targets = targets.to(device)

Pytorch学习_第38张图片
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

from model import *

#定义训练的设备
#device = torch.device("cpu")
device = torch.device("cuda:0")
#device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

#准备数据集
train_data = torchvision.datasets.CIFAR10('data', train=True, transform=torchvision.transforms.ToTensor(), download=True)
test_data = torchvision.datasets.CIFAR10('data', train=False, transform=torchvision.transforms.ToTensor(), download=True)

#数据集长度
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集长度:{}".format(train_data_size))
print("测试数据集长度:{}".format(test_data_size))

#加载数据集(Dataloader)
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

#创建网络模型
tudui = Tudui()
tudui = tudui.to(device)

#损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)

#优化器
#learning_rate = 0.01
learning_rate = 1e-2
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)

#参数
total_train_step = 0
total_test_step = 0
epoh = 10

#添加tensorbooard
writer = SummaryWriter("logs/trainlogs")

for i in range(epoh):
    print("-----第{}轮训练-----".format(i+1))

    #训练开始
    tudui.train()#模型状态
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        outputs = tudui(imgs)
        loss = loss_fn(outputs, targets)

        #优化器调优
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_train_step = total_train_step + 1
        if total_train_step % 100 == 0:
            print("训练次数:{},Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)

    #测试
    tudui.eval()#模型状态
    total_test_loss = 0
    total_accuracy = 0
    with torch.no_grad():
        for data in test_dataloader:
            imgs, targets = data
            imgs = imgs.to(device)
            targets = targets.to(device)
            outputs = tudui(imgs)
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()
            accuracy = (outputs.argmax(1) == targets).sum()
            total_accuracy = total_accuracy + accuracy

    print("测试集loss:{}".format(total_test_loss))
    print("测试集正确率: {}".format(total_accuracy / test_data_size))
    total_test_step = total_test_step + 1
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)

    torch.save(tudui, "model/tudui_{}.pth".format(i))

writer.close()

模型正向推理

模型推理(测试、demo),利用已经训练好的模型,然后给它提供输入。

import torch
import torchvision
from PIL import Image
from model import *

img_path = "test_imgs/1.jpg"
image = Image.open(img_path)
image = image.convert('RGB') # 变成3通道

transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),
                                            torchvision.transforms.ToTensor()])
image = transform(image)

# class Tudui(nn.Module):
#     def __init__(self):
#         super(Tudui, self).__init__()
#         self.model = nn.Sequential(
#             nn.Conv2d(3, 32, 5, 1, 2),
#             nn.MaxPool2d(2),
#             nn.Conv2d(32, 32, 5, 1, 2),
#             nn.MaxPool2d(2),
#             nn.Conv2d(32, 64, 5, 1, 2),
#             nn.MaxPool2d(2),
#             nn.Flatten(),
#             nn.Linear(1024, 64),
#             nn.Linear(64, 10)
#         )
# 
#     def forward(self, x):
#         x = self.model(x)
#         return x

model = torch.load("model/tudui_9.pth", map_location=torch.device('cpu'))
#如果权重是在GPU上训练的,现在要在CPU上做前向推理,那么就要加上这个参数map_location=torch.device('cpu')

image = torch.reshape(image, (1, 3, 32, 32)) #模型的输入必须有一个batchsize
model.eval() #进入推理状态,防止有BN或者Dropout推理出现问题
with torch.no_grad(): # 节约内存和性能
    output = model(image)
print(output)
print(output.argmax(1)) # 返回概率最大的类别序号
所有类别序号:
'airplane'=0,'automobile'=1,'brid'=2,'cat'=3,'deer'=4,
'dog'=5,'frog'=6,'horse'=7,'ship'=8,'truck'=9

开源项目

运行python文件除了在pycharm里运行以外,还可以通过指令在终端直接运行。小土堆最后一节课简单讲了

我的理解是前面的python train.py是让我的代码运行起来,后面的dataroot等等是一些参数传入进去,如果想省事简单一点,就修改代码里的参数,全部加一个default默认值,这样就不需要指定参数传入了,自动使用默认值。

你可能感兴趣的:(深度学习自学,python,学习)