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)
提供一种方式去获取数据及其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]) #这个函数可以根据不同系统自动合并路径,不同系统斜杠不同
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是一个可迭代的数据装载器,组合了数据集和采样器,并在给定数据集上提供可迭代对象。可以完成对数据集中多个对象的集成。官方文档
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整除时, 是否舍弃最后一批数据
使用CIFAR10的测试数据集来完成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。
在dataset中,每一个数据样本元组由一张图片对象img和一个标签target组成;
而dataloader中会分别对一个批次中的图片和标签进行打包,因此dataloader中,每一个对象元组由batchsize张图片对象imgs和batchsize个标签targets组成。(注意取数据的时候并不是按顺序取的,采样器sampler默认是随机采样,也就是说抓取四张图片的时候是随机取的)
对一个batchsize批次中的所有图片对象进行打包,形成一个对象,我们叫它imgs
对一个batchsize批次中所有的标签进行打包,形成一个对象,我们叫它targets
我们需要通过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,组合到一起了。
修改数据集的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张图片。
每一轮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中的图片不同了,说明每一轮大循环开始前都在数据集中重新打乱了顺序。
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]) #这个函数可以根据不同系统自动合并路径 |
conda install tensorboard 安装
from torch.utils.tensorboard import SummaryWriter
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下面所有的事件文件,然后重新运行,并且重新进入终端输入命令。
可接受的图像格式: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。打开链接就能看到图片了。
from torchvision import transforms
transforms是一个处理图片的工具箱。
用PIL和cv2读取的图片都是HWC格式的(0~255),ToTensor会自动转换成CHW格式(0~1)。
class ToTensor:
"""Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor. """
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])
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])
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查看。
输入必须是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)
输入必须是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)
可以把几个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()
作用:把图像按照随机位置进行裁剪。
参数需要输入想要裁剪成的图片大小。
如果输入的是序列(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模块提供了需要常用的数据集以及其具体的使用方法,比如下图所示的图像分类中常用的CIFAR10数据集,图像检测中常用的COCO数据集等。
以CIFAR10为例
CIFAR-10是一个更接近普适物体的彩色图像的小型数据集。 一共包含10 个类别的RGB 彩色图片:飞机( airplane )、汽车( automobile )、鸟类( bird )、猫( cat )、鹿( deer )、狗( dog )、蛙类( frog )、马( horse )、船( ship )和卡车( truck )。每个图片的尺寸为32 × 32 ,每个类别有6000个图像,数据集中一共有50000 张训练图片和10000 张测试图片。
需要设定的5个参数:
1. root(字符串型):把数据集下载到的位置路径。
2. train(布尔型):是否把该数据集作为训练数据集使用。
True: 作为训练数据集创建
False:不作为训练数据集,作为测试数据集创建
3. transform:图像需要进行的变换操作,一般使用compose把所需的transforms结合起来。
4. target_transform:对于标签需要做的变换
5. download(布尔型):是否下载数据集。
True:把数据集下载到root指定的对应位置;如果数据集以及进行过下载,则不会再一次下载
False:不下载数据集
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. 点击运行,等待一段时间后显示下载成功
3. 观察项目包目录,可以发现自动创建了名为dataset3的文件夹,下载的解压文件和解压好的数据集都在其中。
如果在pycharm中下载速度很慢的话,可以找到pycharm所用的下载链接,然后自己使用迅雷等下载软件进行快速下载。
如何找到下载链接?
把鼠标移动到想要下载的数据集名称上,然后Ctrl+C,进入该数据集的帮助文档。
2. 可以看到对应的下载文件名和下载链接。
3. 使用迅雷或者浏览器下载,然后把下载过后的压缩文件按照root中定义的路径创建文件夹,然后把文件放入文件夹中,注意,自己创建的文件夹一定要和root中定义的文件夹姓名相同才行,否则后期扫描不到该数据集。
4. 运行上面在线下载中定义的语句,可以发现程序不会再次下载数据集文件,而是会帮你解压好数据集。
无论是否需要在线下载数据集,都推荐把download参数值设为True。
因为程序可以帮你自动完成下载解压工作,就算自己下载过文件,也可以提供解压功能,因此更加方便。
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()方法进行图片的可视化显示
因为需要完成数据集中所有图片类型从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)
VGG地址 可以分类1000个类别,因为ImageNet就是1000类。
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)
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图片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)
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)
我们自己定义的神经网络需要继承nn.Module类,需要重写以下两个方法:
init方法:初始化
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))
自定义名为Ysy的神经网络,继承自nn.Module
重写init和forward两个方法,在forward中定义该神经网络的规则,即输入+1为输出
实例化神经网络
把tensor类型的值x放入神经网络中进行输出
成功输出结果为 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就行。
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( input, weight, bias=None, stride=1, padding=0,
dilation=1, groups=1) → Tensor
weight就是卷积核。
stride步长参数可以是元组,一个是H方向的步长,一个是W方向的步长。
padding可以是一个元组,纵向和横向padding的参数。
方法中对input和weight要求的shape都需要是4维的。
minibatch:batch中的样例个数
in_channels:每个样例数据的通道数
iH:每个样例的高(行数)
iW:每个样例的宽(列数)
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)
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)
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
dilation:空洞卷积,进行卷积操作时会隔n个取一个。
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)
stride的默认大小为池化核的大小
ceil_mode:
遇到这种不足9个数的情况时是否要对这6个数进行池化,默认False就不管了,True就要池化这6个数。
MaxPool2d不支持输入的tensor的数据类型是long,会报错。
最大池化的作用和目的
作用:最大限度的保留图片特征,同时 减少数据量,加速训练速度。
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()
一般卷积网络里的tensor的shape都是(N,C,H,W),所以concatenate的时候想拼接通道就应该是dim=1,在C的维度进行拼接。
作用:非线性变换的主要目的就是给网中加入一些非线性特征,非线性越多才能训练出符合各种特征的模型。
CLASStorch.nn.ReLU(inplace=False)
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)
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()
CLASS torch.nn.BatchNorm2d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, device=None, dtype=None)
nn.BatchNorm2d(channel)
BN层里的参数也是可以学习的。
基于公式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])
构建一个序列化的container,可以把想要在神经网络中添加的操作都放进去,按顺序进行执行。
把卷积、非线性激活、卷积、非线性激活使用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
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
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()
CLASS torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
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)
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)
注意这个交叉熵函数里面包括了softmax最后的归一化处理,所以输入给它的数得是没经过softmax归一化的数据。
CLASS torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=- 100, reduce=None, reduction='mean', label_smoothing=0.0)
这张图用softmax的交叉熵来演示,至于为什么写的这么复杂,是因为这里写的[0.1,0.2,0.3]并不是softmax归一化之后的概率,而是输入给softmax的数(得分),因此损失函数也要加一个softmax归一化,softmax的损失函数就是-log y_hat。
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里面除了保存了权重还可能保存了优化器参数,epoch,loss等等参数。为啥我保存的权重文件那么大? 如何保存与加载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)
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文件
用上面这个简单模型给CIFAR10数据集分类,10个类别。
通常模型都单独放在一个python文件里一般是model
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必须在同一目录下
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时常用到。
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])横着找最大值的序号
对BN和dropout有作用,其他的好像没什么用
对BN和dropout有作用,其他的好像没什么用
在这些东西后面加.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)
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默认值,这样就不需要指定参数传入了,自动使用默认值。