PyTorch
一、torch学习
1、两个工具
用于探索Python或pytorch中的工具包
- dir():能让我们知道工具箱中有什么东西
- help():告诉我们工具的使用方法
2、pycharm 和 jupyter的区别
3、pytorch加载数据
01.初认识
-
Dataset
- 提供一种方式去获取数据及其label
- 如何获取每一个数据及其label
- 告诉我们总共有多少个数据
- 提供一种方式去获取数据及其label
-
Dataloader
- 为后面的网络提供不同的数据形式
-
注:类中–init-- (self)初始化函数
#类首先要初始化,即根据这个类创建实例的时候自动调用的函数
#作用:为class提供全局变量,为后面的函数提供他们所需要的 量
import os.path
from torch.utils.data import Dataset
from PIL import Image
class Mydata(Dataset):
def __init__(self,root_dir,label_dir,):
self.root_dir=root_dir
self.label_dir=label_dir
self.path=os.path.join(self.root_dir,self.label_dir)
self.img_path = os.listdir(self.path)
def __getitem__(self,idx):
img_name =self.img_path[idx]
img_item_path=os.path.join(self.root_dir,self.label_dir,img_name)
img = Image.open(img_item_path)
label = self.label_dir
return img,label
def __len__(self):
return len(self.img_path)
root_dir = "pytorch数据/hymenoptera_data/train"
ants_label_dir = "ants"
ants_dataset = Mydata(root_dir,ants_label_dir)
img,label = ants_dataset[0]
img.show()
- output:
02.简单理解
1`.Dataloader()
-
概念:是一个迭代器,方便我们去多线程地读取数据,并且可以实现batch以及shuffle的读取等
-
用法:
test_loader=DataLoader(dataset=test_data, batch_size=4, shuffle=True, num_workers=0, drop_last=True)
- 如上,test_loader 就是我们生成的数据迭代器,
- 即加载test_data数据集,每次打包四个数据,打包成imgs和targets,
- shuffle表示每次迭代完之后,下次迭代是否打乱顺序
- drop_list 表示是否删除非完整页的结尾数据
- 完整参数参考官方文档。
-
完整版:
DataLoader(dataset, batch_size=1, shuffle=False, 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)
dataset
:指定要加载数据的数据集,通常是一个Dataset
对象。batch_size
:指定每个批次中样本的数量,默认为1。shuffle
:指定是否在每个epoch开始时对数据进行洗牌,默认为False。sampler
:可选参数,指定用于抽样数据的抽样器。batch_sampler
:可选参数,指定用于生成批次的批次抽样器。num_workers
:指定用于数据加载的子进程数,默认为0,表示所有的数据加载将在主进程中进行。collate_fn
:指定用于将样本列表组装成一个批次的函数,默认为None
,表示使用默认的方式进行组装。pin_memory
:指定是否将加载的数据存储在CUDA固定内存中,默认为False。drop_last
:指定是否丢弃最后一个不完整的批次,默认为False。timeout
:指定数据加载器在等待数据时的超时时间,默认为0。worker_init_fn
:可选参数,指定每个工作进程在启动时要运行的初始化函数。multiprocessing_context
:指定在多进程加载数据时使用的上下文,默认为None
。
import torchvision #准备的测试数据集 from torch.utils.data import DataLoader from torch.utils.tensorboard import SummaryWriter test_data = torchvision.datasets.CIFAR10(root="./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True) test_loader = DataLoader(dataset=test_data,batch_size=64,shuffle=True,num_workers=0,drop_last=False) #测试数据集中第一张图片及target target是标签 img,target = test_data[0] print(img.shape) print(target) writer = SummaryWriter("dataloader") step = 0 for data in test_loader: imgs,targets =data # print(imgs.shape) # print(targets) writer.add_images("test_data",imgs,step) step +=1 writer.close()
2`.小总结
- Dataset是一个包装类,用来将数据包装为Dataset类,然后传入DataLoader中
- 我们再使用DataLoader这个类来更加快捷的对数据进行操作。
3`.数据组织形式
- 如一个文件夹内存放多个同类的图片:文件夹的名称就是其label
- 数据和label存放在不同的文件夹内
4.可视化工具Tensorboard—针对模型训练
- 因为我们编写出来的TensorFlow或pytorch(1.0之后添加了这个模块)程序,建好一个神经网络,
- 其实我们也不知道神经网络里头具体细节到底做了什么,要人工调试十分困难(就好比你无法想象出递归的所有步骤一样)。
- 有了TensorBoard,可以将TensorFlow程序的执行步骤都显示出来,非常直观。并且,我们可以对训练的参数(比如loss值)进行统计,用图的方式来查看变化的趋势。
01.Summarywrite类:
-
SummaryWriter
类提供了一个高级API来创建一个事件文件,在给定的目录中添加摘要和事件。#先要添加一个头文件 from torch.utils.tensorboard import SummaryWriter # 一般要先创建一个实例 writer = SummaryWriter("logs") #一般运用的方法 writer.add_image() writer.add_scalar() writer.close()#最后要关闭
#命令行中显示事件文件的端口地址:tensorboard --logdir = 事件文件所在文件夹名 #修改地址:tensorboard --logdir=logs --port=6007 #某些情况可能会有发生事件冲突,造成图像混乱(如修改变量时没有修改标签):这时可以将事件删除重新运行
02.add_scalar()方法:
add_scalar(tag, scalar_value, global_step=None, walltime=None)
参数:
tag
(str):标量值的名称,用于在 TensorBoard 中标识该值的类型。scalar_value
(float):要记录的标量值。global_step
(int, optional):可选参数,用于指定记录标量值的全局步数,通常表示训练的迭代次数或批次数。如果不指定,则默认为当前时间戳。walltime
(float, optional):可选参数,用于指定记录标量值的时间戳。如果不指定,则默认为当前时间戳。
返回值:
- 无,直接将标量值添加到 SummaryWriter 对象的日志中。
说明:
- 此方法用于向 TensorBoard 日志中添加单个标量值,用于记录训练过程中的损失、准确率等指标。
tag
参数用于在 TensorBoard 中标识该值的类型,可以自定义标签名称。global_step
参数通常用于指定记录标量值的全局步数,以便在 TensorBoard 中横轴上对标量值进行排序。walltime
参数用于指定记录标量值的时间戳,如果不指定,则默认为当前时间戳。
示例:
import torch
from torch.utils.tensorboard import SummaryWriter
# 创建 SummaryWriter 对象,指定日志保存的路径
writer = SummaryWriter(log_dir='logs')
# 模拟训练过程,每个 epoch 结束时记录损失值和准确率
for epoch in range(1, 11): # 假设训练了10个epoch
# 模拟训练损失值和准确率
train_loss = 0.5 / epoch # 以简单的函数模拟训练损失值
train_accuracy = 0.7 + 0.1 * epoch # 以简单的函数模拟训练准确率
# 使用 add_scalar() 方法将损失值和准确率写入日志
writer.add_scalar('Train/Loss', train_loss, epoch)
writer.add_scalar('Train/Accuracy', train_accuracy, epoch)
# 关闭 SummaryWriter 对象
writer.close()
- 在这个示例中,使用了
add_scalar()
方法将每个 epoch 的训练损失值和准确率写入到 TensorBoard 日志中。在实际应用中,train_loss
和train_accuracy
应该是在实际训练过程中计算得到的真实值。通过将这些标量值记录到 TensorBoard 日志中,可以在 TensorBoard 中轻松地查看损失曲线和准确率曲线,以评估模型的训练情况。
打开logs文件方法
- 在Pycharm中的终端里面输入
tensorboard --logdir=文件名
,再点击后面生成的链接- logdir:事件文件所在文件夹名
- 一般要指定端口,在后面加
--port=6007(或其他数)
避免和其他人的端口冲突
03.add_image()方法:
- 在事件文件中添加图片(本次加载的图片为PIL类型,不符合类型要求,所以要转换,可用OpenCV或numpy直接转换)
add_image(tag, img_tensor, global_step=None, walltime=None, dataformats='CHW')
-
参数:
tag
(string):用于标识图像的名称。img_tensor
(Tensor):包含图像数据的张量。该张量的形状应为[batch_size, channels, height, width]
。- 类型为:torch.Tensor,numpy.array,string/blobname
global_step
(int, optional):用于标识该事件的全局步数。如果不提供该参数,则默认使用内部计数器。walltime
(float, optional):事件发生的时间戳。如果不提供该参数,则默认使用当前时间。dataformats
(string, optional):图像数据的格式。支持的格式包括'CHW'
(默认) 和'HWC'
。
-
描述: 将图像数据添加到 TensorBoard 中,以便进行可视化。
-
返回值: 无。
-
示例:
writer.add_image('MNIST Images', images, global_step=0)
在上述示例中,tag
参数是用于标识图像的名称,img_tensor
参数是包含图像数据的张量,global_step
参数是可选的,用于指定事件的全局步数,walltime
参数是可选的,用于指定事件发生的时间戳,dataformats
参数是可选的,用于指定图像数据的格式,默认为 'CHW'
格式。
- 所以从PIL到numpy,需要在add_image()中指定shape中每一个数字/维表示的含义
1`将图片转化为numpy格式
1``利用opencv
2``利用 numpy.array()
-
先导入numpy
-
再通过
np.array()
将PIL格式转化为numpy格式import numpy as np img_array=np.array(img)
from torch.utils.tensorboard import SummaryWriter
from PIL import Image
import numpy as np
writer = SummaryWriter("logs")
image_path = "pytorch数据/hymenoptera_data/train/ants/0013035.jpg"
img_PIL = Image.open(image_path)
img_array = np.array(img_PIL)
writer.add_image("test",img_array,1,dataformats='HWC')
writer.close()
5.transforms结构及用法
01.什么是transforms?
-
常用的图像预处理方法 ,一般用于转换图片格式
有多个图片处理方法,如:-
ToTensor()对象可传入两种图片格式:
-
PIL:用PIL的Image工具打开
-
numpy:用OpenCV打开
-
-
02.transforms该如何使用?
-
首先创建一个具体的工具(如ToTensor工具,相当于创建类对象):
tool = transforms.ToTensor()
-
然后给工具传入参数(传入图片):
result = tool(input)
-
最后得到tensor类型的图片
from torchvision import transforms from PIL import Image img_path="pytorch数据/hymenoptera_data/train/ants/0013035.jpg" img = Image.open(img_path) tensor_trans = transforms.ToTensor()#创建具体工具 tensor_img = tensor_trans(img)#使用工具转换图片格式
1`为什么我们需要Tensor数据类型?
- tensor类型中的很多属性我们都需要在神经网络中用到,如反向传播、梯度等
- 所以我们必定要用到transforms将数据转换为tensor类型,然后进行训练
03.常见的transforms
-
可直接去查看文档里查看其相关方法的及其用法
-
注:Python类中 ——call—— 方法的用法:
- 相当于类对象的有参构造
def --call-- (self, 参数列表)
class Person: def __call__(self,name): print("__call__"+"hello"+name) def hello(self,name): print("hello",name) person = Person() person("zhangsan") person.hello("list") """ output: __call__hellozhangsan hello list """
- 相当于类对象的有参构造
1`ToTensor 方法的使用:
- 前面讲过,先把图片打开为PIL类型或者numpy类型的对象
然后将对象传入创建好的ToTensor工具,将图片格式转为Tensor类型
2`Normalize-归一化的使用:
-
公式:
transform.Normalize(mean,std) """ mean:对应RGB三个通道的均值 std:对应RGB三个通道的标准差 """
import torchvision.transforms as transforms # 定义均值和标准差 mean = [0.5, 0.5, 0.5] # 对应RGB三个通道的均值 std = [0.5, 0.5, 0.5] # 对应RGB三个通道的标准差 # 创建transform normalize = transforms.Normalize(mean=mean, std=std) # 使用transform transform = transforms.Compose([ transforms.ToTensor(), # 将图像转换为Tensor normalize # 对图像进行标准化处理 ]) # 应用transform normalized_image = transform(image)
-
归一化的计算公式:
input[channel] = (input[channel] - mean[channel])/std[channel]
-
目的:改变图像的像素范围,
-
用法:假设是三通道的图片,且像素范围是(0,1)
trans_norm=transforms.Normalize([0.5,0.5,0.5],[0.5,0.5,0.5])
先实例化一个Normalize对象,然后传入两个参数,一个均值,一个标准差(都是三通道的),结果就将图片的像素范围变成(-1,1) -
注:只有tensor类型的图片能使用这个方法,所以将上边ToTensor的图片直接使用即可。
from PIL import Image
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs") # Corrected initialization
img = Image.open("pytorch数据/images/pytorch.jpg")
# Totensor
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img)
writer.add_image("Totensor", img_tensor)
# Normalize
print(img_tensor[0][0][0])
trans_norm = transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
img_norm = trans_norm(img_tensor)
print(img_norm[0][0][0])
writer.close()
"""
output:
tensor(0.8471)
tensor(0.6941)
"""
3`Resize-图片缩放:
- 目的:Resize the input PIL Image to the given size, and return a PIL Image
- API:
transforms.Resize(size, interpolation=2)
size
(sequence 或 int):所需的输出大小。如果 size 是类似 (h, w) 的序列,则输出大小将与此匹配。如果大小为整数,则图像的较小边缘将与此数字匹配,保持纵横比。即,如果高度>宽度,则图像将重新缩放为(大小 \times \text{height} / \text{width},大小)。例如,表示图像的较短边将调整为 256,较长的边将按比例调整大小。interpolation
(int,可选):所需的插值。默认值为(双线性插值)。其他选项包括:PIL.Image.NEAREST
(最近邻插值)PIL.Image.BILINEAR
(双线性插值)PIL.Image.BICUBIC
(双三次插值)PIL.Image.LANCZOS
(Lanczos 重采样)
- 用法:同样可以输入参数列表或一个参数,一个参数的话代表缩放为参数大小的正方形
trans_resize = transforms.Resize((512,700))
#传入一个参数列表,实例化一个resize对象对图片进行缩放img_resize = trans_resize(img)
#传入一个PIL图片- 最后再将输出的图片转为tensor类型用SummaryWriter进行输出
from PIL import Image
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")
img = Image.open("pytorch数据/images/pytorch.jpg")
print(img)
# Totensor
trans_totensor = transforms.ToTensor()
img_tensor = trans_totensor(img)
writer.add_image("Totensor", img_tensor)
# Normalize
print(img_tensor[0][0][0])
trans_norm = transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
img_norm = trans_norm(img_tensor)
print(img_norm[0][0][0])
writer.add_image("Normalize",img_norm,2)
#Resize
trans_resize = transforms.Resize((512,512))
# img PIL -> resize -> img_resize PIL
img_resize = trans_resize(img)
print(img_resize)
# img_resize PIL -> totensor -> img_resize tensor
img_resize = trans_totensor(img_resize)
writer.add_image("Resize",img_resize)
"""
output:
<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=474x266 at 0x21B1D576400>
tensor(0.8471)
tensor(0.6941)
<PIL.Image.Image image mode=RGB size=512x512 at 0x21B1D5E1D90>
"""
4`Compose-组合方法的使用
-
目的:将几个转换组合在一起。此转换不支持torchscript。输入是一个transforms对象的列表相当于简化操作步骤(本质上相当于一个合并的功能,按照写的列表去执行流程)
-
用法:
- 还是先实例化对象,然后传参
- 传入transforms对象列表进行实例化
transform = transforms.Compose([ transforms.Resize(256), transforms.RandomCrop(224), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ])
# Compose - resize - 2 trans_resize_2 = transforms.Resize(512) trans_compose = transforms.Compose([trans_resize_2,trans_totensor]) # PIL -> PIL -> tensor img_resize_2 = trans_compose(img) writer.add_image("Resize",img_resize_2,1)
-
注:组合的transforms后一个对象的输入要和前一个对象的输出一致
5`RandomCrop–随机裁剪
-
公式:
transforms.RandomCrop(size, padding=None, pad_if_needed=False, fill=0, padding_mode='constant')
size
:指定裁剪后的图像尺寸,可以是一个整数(表示将图像裁剪为正方形),也可以是一个tuple(height, width)
,表示裁剪后的高度和宽度。padding
:可选参数,指定在裁剪之前在图像周围填充的像素数。如果提供了这个参数,图像将首先填充,然后进行随机裁剪。pad_if_needed
:可选参数,如果设置为True,并且输入图像的尺寸小于要求的裁剪尺寸,则会进行填充。fill
:可选参数,用于填充的像素值。padding_mode
:可选参数,指定填充的方式,可以是'constant'
、'edge'
、'reflect'
或'symmetric'
。
-
目的:在一个随机的位置裁剪给定的图像。返回的结果也是一个PIL,可以传一个参数或两个通道的列表。
-
用法:与上边的使用类似
#RandomCrop trans_random = transforms.RandomCrop(64) trans_compose_2 = transforms.Compose([trans_random,trans_totensor]) for i in range(10): img_crop = trans_compose_2(img) writer.add_image("RandomCrop",img_crop,i)
6`小结
-
多关注输入和输出类型
-
不会的多看官方文档
-
关注方法需要什么参数
-
不知道返回值的时候:
- print(type())
- debug
6.torchvision中的数据集使用
-
去pytorch官方文档找torchvision的datasets模块,里面有很多开元的数据集,可以在代码里直接使用和下载,方法参考官方文档。
-
下载数据集的时候,download选项可以一直为True,因为已下载的不会重复下载。
-
下载数据集的同时可以转换整个数据集的数据类型:在每个torchvision.datasets下的数据集都会有transforms选项,即transforms转换的实例对象。
代码如下:
import torchvision
from torch.utils.tensorboard import SummaryWriter
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
train_set = torchvision.datasets.CIFAR10(root="./dataset",transform=dataset_transform,train=True,download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset",transform=dataset_transform,train=False,download=True)
# print(test_set[0])
# print(test_set.classes)
#
# img,target = test_set[0]
# print(img)
# print(target)
# print(test_set.classes[target])
# img.show()
# print(test_set[0])
writer = SummaryWriter("p10")
for i in range(10):
img,target = test_set[i]
writer.add_image("test_set",img,i)
writer.close()
7.搭建神经网络
0.1神经网络的基本骨架–nn.Module的使用
-
注·:Python中类的定义时,类的继承直接在括号里添加
class 类名(继承的类) -
nn(natural network)下的container是nn的容器,包含了基本骨架
-
其中container中的nn.Module是所有神经网络模块最基本的一个类,为所有神经网络提供基本骨架,
然后再其中进行填充就能形成一个神经网络 -
forward是一个前向的神经网络处理器(一般会对其进行重写)
-
如果神经网络要重写初始方法,则必须要调用父类的初始化函数
-
总结:
- 一个nn.module可以视为一个块。所有的module包含两个主要函数:
init函数:在里边定义一些需要的类或参数。包括网络层
forward函数:做最终的计算和输出,其形参就是模型(块)的输入。
import torch
from torch import nn
class King(nn.Module):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
def forward(self,input):
output = input + 1
return output
king = King()
x = torch.tensor(1.0)
output = king(x)
print(output)
"""
output:
tensor(2.)
"""
02.神经网络–卷积层(torch.nn.conv)
-
torch.nn其实就是对torch.nn.function的进一步封装
-
卷积简介
- 卷积操作是指在输入数据上应用一个滤波器(也称为卷积核或过滤器)进行局部的特征提取。这个滤波器是一个小的矩阵,通过与输入数据进行卷积运算,可以有效地捕捉输入数据中的局部模式或特征**。卷积操作的结果称为特征图或卷积特征。**
- 卷积层通常具有多个滤波器,每个滤波器都学习到不同的特征。通过在不同位置和方向上移动滤波器进行卷积操作,可以在输入数据的不同位置捕捉到不同的特征。这样,卷积层能够提取输入数据的多个特征,并且具有参数共享的特性,可以大大减少模型的参数量,提高模型的泛化能力。
- 卷积层通常还包括其他的组件,例如激活函数和池化操作。激活函数用于引入非线性,增加模型的表达能力;池化操作则用于减少特征图的尺寸,降低计算复杂度,并且增强模型对平移、缩放和旋转等变换的鲁棒性。
- 卷积层的设计和参数设置对神经网络的性能和效果有着重要的影响。通过合理设计卷积核的大小、数量和步长等参数,可以使得神经网络更好地适应不同的数据和任务。因此,卷积层作为神经网络中的核心组件,在图像处理、语音识别等领域有着广泛的应用,并且在深度学习的发展中扮演着重要的角色。
-
conv2d(一般用的比较多)的用法
import torch.nn.functional as F output = F.conv2d(input=x, weight=weight, bias=bias, stride=stride, padding=padding)
input
: 输入的四维张量,形状为[in_channels, batch_size, height, width]
。in_channels
: 表示输入数据的通道数,也称为特征通道数或输入通道数。对于灰度图像来说,通道数为 1,表示图像的灰度信息;对于彩色图像来说,通道数为 3(RGB),表示图像的红、绿、蓝三个通道的信息。batch_size
: 表示一次输入的样本数量,即一批数据中包含了多少个样本。在深度学习中,通常会使用批处理(batch)来训练模型,这样可以提高训练效率和稳定性。height
和width
: 表示输入数据的高度和宽度。对于图像来说,这两个维度分别表示图像的高度和宽度像素数目。在卷积神经网络中,这两个维度通常表示图像的空间尺寸,用于表示图像的形状和结构信息。
weight
: 卷积核的权重参数,形状为[out_channels, in_channels, kernel_height, kernel_width]
。out_channels
是指卷积操作后得到的输出特征图的通道数目。在二维卷积操作中,每个卷积核会生成一个输出通道,因此输出特征图的通道数目就是卷积核的数量,也就是out_channels
。
bias
: 可选的偏置参数,形状为[out_channels]
,默认为None
。stride
: 卷积操作的步长,可以是单个整数或元组(stride_height, stride_width)
。padding
: 卷积操作的填充大小,可以是单个整数或元组(padding_height, padding_width)
,默认为 0。
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]
])
input = torch.reshape(input,(1,1,5,5))
kernel = torch.reshape(kernel,(1,1,3,3))
print(input.shape)
print(kernel.shape)
output1 = F.conv2d(input,kernel,stride = 1)
print(output1)
#设置步长为2
output2 = F.conv2d(input,kernel,stride = 2)
print(output2)
#周围填充一层为0的像素点
output3 = F.conv2d(input,kernel,stride = 1 ,padding=1)
print(output3)
"""
output:
torch.Size([1, 1, 5, 5])
torch.Size([1, 1, 3, 3])
tensor([[[[10, 12, 12],
[18, 16, 16],
[13, 9, 3]]]])
tensor([[[[10, 12],
[13, 3]]]])
tensor([[[[ 1, 3, 4, 10, 8],
[ 5, 10, 12, 12, 6],
[ 7, 18, 16, 16, 8],
[11, 13, 9, 3, 4],
[14, 13, 9, 7, 4]]]])
"""
- output1的具体示例:
- 这边有部分卷积动画可以到该网址下找。
- https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True)
dataloader = DataLoader(dataset,batch_size=64)
class King(nn.Module):
def __init__(self):
super(King,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
king = King()
print(king)
step = 0
writer = SummaryWriter("../logs")
for data in dataloader:
imgs,targets = data
output = king(imgs)
print(imgs.shape)
print(output.shape)
# torch.Size([64, 3, 32, 32])
writer.add_image("input", imgs, step,dataformats="NCHW")
# torch.Size([64, 6, 30, 30]) -> [xxx,3,30,30]
output = torch.reshape(output,(-1,3,30,30))#若第一个数不清楚可以设定为-1
writer.add_image("output", output, step,dataformats="NCHW")
step = step+1
writer.close()
03.池化层–最大池化(torch.nn.maxpool)
-
池化层(Pooling Layer)是深度学习中常用的一种层,用于减少特征图的空间尺寸,降低网络的参数数量,从而减少计算量和内存消耗,同时可以提取出特征的主要信息,有助于防止过拟合。池化层通常跟在卷积层后面,在卷积神经网络(CNN)中广泛应用。
-
池化层的操作是对输入的特征图进行局部区域的聚合操作,通常采用最大池化(Max Pooling)或平均池化(Average Pooling)。**最大池化取输入特征图每个区域的最大值作为输出,而平均池化则取输入特征图每个区域的平均值作为输出。**池化操作通常在每个特征图的非重叠区域内进行,通过设置池化核的大小和步长来控制池化的区域大小和移动步长。
-
池化层的主要作用包括降维、提取主要特征、保持平移不变性(translation invariance)等。通过池化层的操作,可以逐渐减小特征图的空间尺寸,从而使得网络在保留重要信息的同时降低计算成本和内存消耗。
-
最大池化层(常用的是maxpool2d)的作用:
- 一是对卷积层所提取的信息做更一步降维,减少计算量
- 二是加强图像特征的不变性,使之增加图像的偏移、旋转等方面的鲁棒性
- 类似于观看视频时不同的清晰度,实际效果就像给图片打马赛克
-
maxpool2d:注意输入的图像形状为4维,即形状不对时要先reshape
-
具体用法
torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
-
参数:
-
kernel_size
(int or tuple):池化核的大小,可以是单个整数表示正方形核大小,也可以是一个长度为2的元组(H, W)
表示高度和宽度分别的核大小。 -
stride
(int or tuple, optional):池化操作的步长,默认值为kernel_size
。 -
padding
(int or tuple, optional):在输入的每一边添加 0 的层数,默认值为 0。 -
dilation
(int or tuple, optional):卷积核元素之间的间距,默认值为 1。 -
return_indices
(bool, optional):如果为 True,则会返回最大值的索引,用于后向传播,默认值为 False。 -
ceil_mode
(bool, optional):如果为 True,则使用 ceil 而不是 floor 来计算输出形状,默认值为 False。
-
import torch
import torchvision
from torch import nn
from torch.nn import MaxPool2d
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True)
dataloader = DataLoader(dataset,batch_size=64)
# 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]
# ])
# input = torch.reshape(input,(-1,1,5,5))
# print(input.shape)
class King(nn.Module):
def __init__(self):
super(King,self).__init__()
self.maxpool = MaxPool2d(kernel_size=3,ceil_mode = True)
def forward(self,input):
output = self.maxpool(input)
return output
king = King()
# output = king(input)
# print(output)
step = 0
writer = SummaryWriter("logs_maxpool")
for data in dataloader:
imgs,targets = data
writer.add_images("input",imgs,step)
step += 1
output = king(imgs)
writer.add_images("output",output,step)
writer.close()
04.非线性激活(Non-linear Activations)
-
非线性变换的主要目的就是给网中加入一些非线性特征
-
非线性激活函数是神经网络中的一种特殊函数,用于在神经网络中引入非线性特性。在传统的神经网络中,每一层神经元的输出都是上一层神经元输入的加权和,这是一种线性变换。但是,线性变换无法解决复杂的非线性问题,因此需要引入非线性激活函数。
-
非线性激活函数的作用是:
- 引入非线性特性:通过使用非线性激活函数,神经网络可以学习到更复杂的模式,从而解决更复杂的问题。
- 提升模型的表达能力:非线性激活函数可以增加模型的非线性表达能力,使得模型可以更好地拟合复杂的数据分布。
- 防止过拟合:非线性激活函数可以防止过拟合,因为非线性激活函数会使得模型的输出变得不稳定,从而降低模型的过拟合风险。
-
非线性越多才能训练出符合各种特征的模型。常见的非线性激活:
-
ReLU:主要是对小于0的进行截断(将小于0的变为0),图像变换效果不明显
- 主要参数是inplace:
inplace为真时,将处理后的结果赋值给原来的参数;为假时,原值不会改变。
- 主要参数是inplace:
-
SIGMOID: 归一化处理
- 效果没有ReLU好,但对于多远分类问题,必须采用sigmoid
-
-
relu例子:
import torch
from torch import nn
from torch.nn import ReLU
from torch.nn import Module
input = torch.tensor([
[1,-0.5],
[-1,3]
])
input = torch.reshape(input,(-1,1,2,2))
print(input.shape)
class King(nn.Module):
def __init__(self):
super(King,self).__init__()
self.relu1 = ReLU()
def forward(self,input):
output = self.relu1(input)
return output
king = King()
output = king(input)
print(output)
"""
output:
torch.Size([1, 1, 2, 2])
tensor([[[[1., 0.],
[0., 3.]]]])
"""
- SIGMOID例子
import torch
import torchvision
from torch import nn
from torch.nn import Sigmoid
from torch.nn import Module
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True)
dataloader = DataLoader(dataset,batch_size=64)
# input = torch.tensor([
# [1,-0.5],
# [-1,3]
# ])
# input = torch.reshape(input,(-1,1,2,2))
# print(input.shape)
class King(nn.Module):
def __init__(self):
super(King,self).__init__()
self.relu1 =Sigmoid()
def forward(self,input):
output = self.relu1(input)
return output
king = King()
# output = king(input)
# print(output)
step = 0
writer = SummaryWriter("logs_relu")
for data in dataloader:
imgs,targets = data
writer.add_images("input",imgs,step)
step += 1
output = king(imgs)
writer.add_images("output",output,step)
writer.close()
05.线性层(torch.nn.linea)
- 线性层又叫全连接层,其中每个神经元与上一层所有神经元相连,一个简单的线性层如下图所示:
-
线性层是深度学习中常见的一种层,用于将输入数据与权重矩阵相乘并添加偏置项,其作用是对输入数据进行线性变换。在神经网络中,线性层通常用于将输入数据映射到更高维的空间,以便后续的非线性变换可以更好地对数据进行建模。
-
具体来说,线性层的作用可以用以下数学表达式表示:
o u t p u t = i n p u t × w e i g h t + b i a s o u t p u t output=input×weight+biasoutput output=input×weight+biasoutput- 其中,inputinput 是输入数据,weightweight 是权重矩阵,biasbias 是偏置项,outputoutput 是线性层的输出。
-
线性层的主要作用是进行特征的线性组合,通过学习适当的权重和偏置,线性层可以帮助神经网络更好地适应输入数据,从而实现更好的分类、回归或其他任务。然而,单独的线性变换是有限的,通常需要结合非线性激活函数(如ReLU、Sigmoid等)来构建更复杂的神经网络模型,以便学习非线性关系。
-
线性函数为:
torch.nn.Linear(in_features, out_features, bias=True, device=None, dtype=None)
-
其中重要的3个参数in_features、out_features、bias说明如下:
-
in_features:每个输入(x)样本的特征的大小
-
out_features:每个输出(y)样本的特征的大小
-
bias:如果设置为False,则图层不会学习附加偏差。默认值是True,表示增加学习偏置。在上图中,in_features=d,out_features=L。
-
-
作用可以是缩小一维的数据长度
import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.nn import Module
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True)
dataloader = DataLoader(dataset,batch_size=64,drop_last=True)
class King(nn.Module):
def __init__(self):
super(King,self).__init__()
self.linear =Linear(196608,10)
def forward(self,input):
output = self.linear(input)
return output
king = King()
# output = king(input)
# print(output)
step = 0
writer = SummaryWriter("logs_linear")
for data in dataloader:
imgs,targets = data
writer.add_images("input",imgs,step)
step += 1
output = torch.reshape(imgs,(1,1,1,-1))
# 或者使用 torch。flatten()来做一个摊平操作
#output = torch.flatten(imgs)
print(output.shape)
output = king(output)
writer.add_images("output",output,step)
print(output.shape)
writer.close()
06.其他层
- 框中的层在部分特定情形使用较多
- 其他层使用较少,再次不做赘述
07.搭建神经网络小实战与SEQUENTIAL的使用(torch.nn.Sequential)
1`搭建神经网络
- 一些值需要通过公式去计算,如本例中的padding值
2`SEQUENTIAL的使用
Sequential
是PyTorch中一个用于构建神经网络模型的容器,它允许将各种层按顺序堆叠起来以构建神经网络模型。你可以将各种类型的层(如全连接层、卷积层、池化层等)依次传递给Sequential
,然后它会按照你传递的顺序自动构建网络模型。
import torch
from tensorboardX import SummaryWriter
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
class King(nn.Module):
def __init__(self):
super(King, self).__init__()
self.conv1 = Conv2d(3, 32, 5, padding=2)
self.maxpool1 = MaxPool2d(2)
self.conv2 = Conv2d(32, 32, 5, padding=2)
self.maxpool2 = MaxPool2d(2)
self.conv3 = Conv2d(32, 64, 5, padding=2)
self.maxpool3 = MaxPool2d(2)
self.flatten = Flatten()
self.linear1 = Linear(1024, 64)
self.linear2 = Linear(64, 10)
self.model = nn.Sequential(
self.conv1,
self.maxpool1,
self.conv2,
self.maxpool2,
self.conv3,
self.maxpool3,
self.flatten,
self.linear1,
self.linear2
)
def forward(self,x):
x = self.model(x)
return x
king = King()
print(king)
input = torch.ones(64,3,32,32)
output = king(input)
print(output.shape)
writer = SummaryWriter("log_sqe")
writer.add_graph(king,input)
writer.close()
08.损失函数和反向传播
- 注意:inputs和targets的格式一定要符合要求,一般要对其进行reshape和dtype
1`损失函数(Loss):有多种计算方式
-
计算实际输出和目标之间的差距
-
为我们更新输出 提供一定的依据
-
更新输出:
- 反向传播(backward)–>
- 计算出梯度(grade)–>
- 根据梯度和学习率来更新参数–>
- 减小loss
-
损失函数举例:
-
均方方差损失(MSELoss()):
-
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 = L1Loss() result1 = loss(inputs,targets) loss_mse = MSELoss() result2 = loss_mse(inputs,targets) #print(result1) print(result2) #output:tensor(1.3333)
-
L1Loss(): 如图所示,计算的结果
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)) loss = L1Loss() result = loss(inputs,targets) print(result) #output:tensor(0.6667)
-
交叉熵损失(CrossEntropyLoss())
- x是网络输出的数组,class是类别的下标
- 交叉熵损失函数会自动将模型的输出应用Softmax函数,并计算交叉熵损失。因此,在使用交叉熵损失函数时,通常不需要手动在模型的输出之后应用Softmax函数。
import torch from torch.nn import L1Loss, MSELoss, CrossEntropyLoss 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 = L1Loss() result1 = loss(inputs,targets) loss_mse = MSELoss() result2 = loss_mse(inputs,targets) #print(result1) #print(result2) x = torch.tensor([0.1,0.2,0.3]) y = torch.tensor([1]) x = torch.reshape(x,(1,3)) loss_cross = CrossEntropyLoss() result3 = loss_cross(x,y) print(result3) #output:tensor(1.1019)
-
-
2`反向传播:
- 反向传播是一个更新参数的过程。
- (1)前向传播:将训练集数据输入到ANN的输入层,经过隐藏层,最后到达输出层并输出结果。【输入层—隐藏层–输出层】
- (2)反向传播:由于ANN的输入结果与输出结果有误差,则计算估计值与实际值之间的误差,并将该误差从输出层向隐藏层反向传播,直至传播到输入层。【输出层–隐藏层–输入层】
- (3)权重更新:在反向传播的过程中,根据误差调整各种参数的值;不断迭代上述过程,直至收敛。
举一个例子来说明我理解的反向传播的思想是:- (1)前向传播:三个人在玩你画我猜的游戏,然后第一个人给第二个人描述,再将信息传递给第三个人,由第三个人说出画的到底是啥。
- (2)反向传播:第三个人得知自己说的和真实答案之间的误差后,发现他们在传递时的问题差在哪里,向前面一个人说下次描述的时候怎样可以更加准确的传递信息。就这样一直向前一个人告知。
- (3)三个人之间的的默契一直在磨合,然后描述的更加准确。
08.优化器
1`过程描述
-
继上节的计算损失函数和反向传播,
-
之后便是根据损失值,利用优化器进行梯度更新,然后不断降低loss的过程
-
一般要对数据集扫描多遍,进行参数的多次更新,才能得到一个较好的效果。
注意,每次更新后要将梯度置0,然后重新计算梯度注意,每次更新后要将梯度置0,然后重新计算梯度
2`常用优化器:
- 优化器的种类比较多,常用的就是随机梯度下降(SGD) 等
- 不同的优化器的参数列表一般不同,但都会有 params(模型的参数列表)和lr(学习率)参数,
- 一般设置这两个参数,其他的可用默认值
3`举例SGD
import torch
import torch.nn as nn
import torch.optim as optim
# 定义一个简单的神经网络模型
class SimpleModel(nn.Module):
def __init__(self):
super(SimpleModel, self).__init__()
self.fc = nn.Linear(10, 1) # 一个线性层,输入大小为 10,输出大小为 1
def forward(self, x):
return self.fc(x)
# 创建模型实例
model = SimpleModel()
# 定义损失函数
criterion = nn.MSELoss()
# 定义优化器,学习率设置为 0.01
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 假设有输入数据 input_data 和对应的目标数据 target_data
# 模型训练
for epoch in range(num_epochs):
# 前向传播
outputs = model(input_data)
# 计算损失
loss = criterion(outputs, target_data)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
import torch
import torchvision
from tensorboardX import SummaryWriter
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.data import DataLoader
dataset = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True)
dataloader = DataLoader(dataset,batch_size=64)
class King(nn.Module):
def __init__(self):
super(King, self).__init__()
self.model = nn.Sequential(
Conv2d(3, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 32, 5, padding=2),
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self,x):
x = self.model(x)
return x
loss = nn.CrossEntropyLoss()
king = King()
optim = torch.optim.SGD(king.parameters(),lr=0.01)
# lr是学习速率
for epoh in range(20):
running_loss = 0.0
for data in dataloader:
imgs, targets = data
outputs = king(imgs)
result_loss = loss(outputs, targets)
optim.zero_grad() # 将梯度清零
result_loss.backward() # 通过反向传播求出每一个节点的梯度
optim.step() # 对每一个进行调优
# print(result_loss)
running_loss = running_loss + result_loss
print(running_loss)
8.模型训练
01.现有网络模型的使用及修改
1.1、VGG16模型:
vgg16_false = torchvision.models.vgg16(pretrained=False)
vgg16_true = torchvision.models.vgg16(pretrained=True)
-
上面两行代码的区别:
- 第一个是模型初始化的参数,第二个是经过训练的参数
- 第一个相当于一个单纯的 神经网络结构,第二个是经过训练的神经网络结构(需要下载参数)
-
因为VGG16最终的输出是1000个分类,加入我们需要10个分类的话,就需要改动
-
VGG16的原始结构
VGG( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (18): ReLU(inplace=True) (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (25): ReLU(inplace=True) (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (27): ReLU(inplace=True) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(7, 7)) (classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False) (6): Linear(in_features=4096, out_features=1000, bias=True) ) )
-
修改的方法有两种:
-
添加一层(线性层),改变最后的输出类别数
vgg16_true.classifier.add_module("add_linear",nn.Linear(1000,10)) print(vgg16_true) """ output: VGG( (features): Sequential( (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU(inplace=True) (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (3): ReLU(inplace=True) (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (6): ReLU(inplace=True) (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (8): ReLU(inplace=True) (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (11): ReLU(inplace=True) (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (13): ReLU(inplace=True) (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (15): ReLU(inplace=True) (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (18): ReLU(inplace=True) (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (20): ReLU(inplace=True) (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (22): ReLU(inplace=True) (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (25): ReLU(inplace=True) (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (27): ReLU(inplace=True) (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (29): ReLU(inplace=True) (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (avgpool): AdaptiveAvgPool2d(output_size=(7, 7)) (classifier): Sequential( (0): Linear(in_features=25088, out_features=4096, bias=True) (1): ReLU(inplace=True) (2): Dropout(p=0.5, inplace=False) (3): Linear(in_features=4096, out_features=4096, bias=True) (4): ReLU(inplace=True) (5): Dropout(p=0.5, inplace=False) (6): Linear(in_features=4096, out_features=1000, bias=True) (add_linear): Linear(in_features=1000, out_features=10, bias=True) ) ) """
-
改变原有模型的输出
以vgg_false模型为例,要改变下标为6的那一层vgg16_false.classifier[6] = nn.Linear(4096,10)
-
-
02.网络模型的保存和读取
1`概述
- 因为有些较大的网络模型(无论是加载初始参数还是预训练过的参数)都需要花费一定的时间,特别是预训练的模型,要花很长时间下载参数,
- 所以我们可以将反复用到的模型保存下来,到时候直接读取使用即可
2`保存和读取方法
-
一般训练好的模型都需要进行保存,否则每次使用都要重新训练。
-
方式一
-
保存:保存模型结构及其参数。
torch.save(model, path)
torch.save(vgg16,"vgg16_method1.pth")
-
读取:获取一个完整的模型。
torch.load(“模型名”)
(结构和参数皆能获取)model = torch.load("vgg16_method1.pth")
-
-
方式二
-
保存:只保存模型的参数。(将参数以字典的方式存储)
torch.save(model.state_dict(), path)
(官方推荐) -
读取:只能加载出模型的参数,要先新建网络模型,然后再装载参数(一般用于加载预训练的参数)。
vgg16 = torchvision.models.vgg16(pretrained=False) vgg16.load_state_dict(torch.load("vgg16_method2.pth")) # model = torch.load("vgg16_method2.pth") print(vgg16)
-
-
陷阱:自定义的网络如果保存后再加载的话,需要再重新定义一遍网络结构。
03.完整的模型训练套路
- 准备数据集
- 创建迭代器
- 创建网络模型
- 创建损失函数
- 添加优化器
- 设置一些训练的参数(训练次数、训练轮数等),
- 开始模型训练
- 优化器不断优化模型
- 开始测试
- 打印测试结果(准确度)
补充:
1``torch.argmax()`
- torch.argmax()是 PyTorch 中的一个函数,用于找到张量中指定维度上最大值的位置索引。
torch.argmax(input, dim=None, keepdim=False, out=None, dtype=None)
参数说明:
input
:要进行操作的输入张量。dim
:要沿着哪个维度进行操作。如果为 None,则在整个张量中查找最大值的索引,为1则是横向,为2则是纵向。keepdim
:如果为 True,则结果张量保持输入张量的维度。out
:输出张量,用于保存结果。dtype
:输出张量的数据类型。
返回值:
- 返回一个新的张量,包含输入张量中最大值的索引。
import torch
outputs = torch.tensor([
[0.1,0.2],
[0.3,0.4]
])
print(outputs.argmax(1))
#output:
tensor([1, 1])
import torch.optim
import torchvision
from tensorboardX import SummaryWriter
from torch import nn
from torch.utils.data import DataLoader
train_data = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=True,download=True)
test_data = torchvision.datasets.CIFAR10("./dataset",transform=torchvision.transforms.ToTensor(),train=False,download=True)
# 数据集的大小
train_data_size = len(train_data)
test_data_size = len(test_data)
print(f"训练数据集的长度为:{format(train_data_size)}")
print(f"测试数据集的长度为:{format(test_data_size)}")
# 利用daataloader 来加载数据集
train_dataloader = DataLoader(train_data,batch_size=64)
test_dataloader = DataLoader(test_data,batch_size=10)
# 搭建神经网络
class King(nn.Module):
def __init__(self):
super(King, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, 5, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=2),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(1024, 64),
nn.Linear(64, 10)
)
def forward(self,x):
x = self.model(x)
return x
king = King()
# 损失函数 此处是交叉熵
loss_fn = nn.CrossEntropyLoss()
# 优化器
learning_rate = 0.01
optimzer = torch.optim.SGD(king.parameters(),lr=learning_rate)
# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0
# 记录测试的次数
total_test_step = 0
# 训练的轮数
epoch = 10
# 添加tensorboard
writer = SummaryWriter("logs_train")
for i in range(epoch):
print(f"------第 {i+1} 轮训练开始------")
#训练步骤开始
for data in train_dataloader:
imgs,targets = data
outputs = king(imgs)
loss = loss_fn(outputs,targets)
# 优化器优化模型
optimzer.zero_grad()
loss.backward()
optimzer.step()
total_train_step+=1
if total_train_step % 100 ==0:
print(f"训练次数:{total_train_step},Loss: {loss}")
writer.add_scalar("train_loss",loss.item(),total_train_step)
# 测试步骤开始
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs,targets = data
outputs = king(imgs)
loss = loss_fn(outputs, targets)
total_test_loss += loss
accuracy = (outputs.argmax(1) ==targets).sum()
total_accuracy += accuracy
print(f"整体测试集上的Loss:{total_test_loss}")
print(f"整体测试集上的正确率:{total_accuracy/test_data_size}")
writer.add_scalar("test_loss",total_test_loss,total_test_step)
writer.add_scalar("test_accuracy",(total_accuracy/test_data_size),total_test_step)
total_test_step+=1
torch.save(king,"king_{}.pth".format(i))
print("模型已保存")
writer.close()
"""
output:
训练数据集的长度为:50000
测试数据集的长度为:10000
------第 1 轮训练开始------
训练次数:100,Loss: 2.2905163764953613
训练次数:200,Loss: 2.281721353530884
训练次数:300,Loss: 2.252750873565674
训练次数:400,Loss: 2.1662673950195312
训练次数:500,Loss: 2.0202040672302246
训练次数:600,Loss: 1.976378321647644
训练次数:700,Loss: 2.002121686935425
整体测试集上的Loss:1956.302734375
整体测试集上的正确率:0.30079999566078186
模型已保存
------第 2 轮训练开始------
训练次数:800,Loss: 1.848758339881897
训练次数:900,Loss: 1.7890944480895996
训练次数:1000,Loss: 1.8642159700393677
训练次数:1100,Loss: 1.963551640510559
训练次数:1200,Loss: 1.671011209487915
训练次数:1300,Loss: 1.59224271774292
训练次数:1400,Loss: 1.7198998928070068
训练次数:1500,Loss: 1.7643325328826904
整体测试集上的Loss:1887.615478515625
整体测试集上的正确率:0.32919999957084656
模型已保存
------第 3 轮训练开始------
训练次数:1600,Loss: 1.7233344316482544
训练次数:1700,Loss: 1.6460649967193604
训练次数:1800,Loss: 1.9495211839675903
训练次数:1900,Loss: 1.6880135536193848
训练次数:2000,Loss: 1.8684566020965576
训练次数:2100,Loss: 1.5057942867279053
训练次数:2200,Loss: 1.4742074012756348
训练次数:2300,Loss: 1.766707420349121
整体测试集上的Loss:1669.1881103515625
整体测试集上的正确率:0.39800000190734863
模型已保存
------第 4 轮训练开始------
训练次数:2400,Loss: 1.7554244995117188
训练次数:2500,Loss: 1.3697314262390137
训练次数:2600,Loss: 1.564285397529602
训练次数:2700,Loss: 1.7147036790847778
训练次数:2800,Loss: 1.4876322746276855
训练次数:2900,Loss: 1.5656487941741943
训练次数:3000,Loss: 1.337398648262024
训练次数:3100,Loss: 1.5050169229507446
整体测试集上的Loss:1603.9476318359375
整体测试集上的正确率:0.4185999929904938
模型已保存
------第 5 轮训练开始------
训练次数:3200,Loss: 1.3732656240463257
训练次数:3300,Loss: 1.4424837827682495
训练次数:3400,Loss: 1.496065616607666
训练次数:3500,Loss: 1.530358076095581
训练次数:3600,Loss: 1.5837149620056152
训练次数:3700,Loss: 1.3562780618667603
训练次数:3800,Loss: 1.2520641088485718
训练次数:3900,Loss: 1.4492415189743042
整体测试集上的Loss:1554.6851806640625
整体测试集上的正确率:0.43479999899864197
模型已保存
------第 6 轮训练开始------
训练次数:4000,Loss: 1.392557144165039
训练次数:4100,Loss: 1.4287751913070679
训练次数:4200,Loss: 1.5785491466522217
训练次数:4300,Loss: 1.1886277198791504
训练次数:4400,Loss: 1.1640338897705078
训练次数:4500,Loss: 1.3129788637161255
训练次数:4600,Loss: 1.4037131071090698
整体测试集上的Loss:1473.1033935546875
整体测试集上的正确率:0.4634999930858612
模型已保存
------第 7 轮训练开始------
训练次数:4700,Loss: 1.327232003211975
训练次数:4800,Loss: 1.5121268033981323
训练次数:4900,Loss: 1.349504828453064
训练次数:5000,Loss: 1.4058730602264404
训练次数:5100,Loss: 1.000952124595642
训练次数:5200,Loss: 1.2390520572662354
训练次数:5300,Loss: 1.185653567314148
训练次数:5400,Loss: 1.3723623752593994
整体测试集上的Loss:1397.11767578125
整体测试集上的正确率:0.4909999966621399
模型已保存
------第 8 轮训练开始------
训练次数:5500,Loss: 1.2305434942245483
训练次数:5600,Loss: 1.23661470413208
训练次数:5700,Loss: 1.2103543281555176
训练次数:5800,Loss: 1.2133100032806396
训练次数:5900,Loss: 1.3391343355178833
训练次数:6000,Loss: 1.5014292001724243
训练次数:6100,Loss: 1.0114054679870605
训练次数:6200,Loss: 1.1058776378631592
整体测试集上的Loss:1325.4554443359375
整体测试集上的正确率:0.5213000178337097
模型已保存
------第 9 轮训练开始------
训练次数:6300,Loss: 1.405073642730713
训练次数:6400,Loss: 1.1180084943771362
训练次数:6500,Loss: 1.5476839542388916
训练次数:6600,Loss: 1.0472126007080078
训练次数:6700,Loss: 1.1250394582748413
训练次数:6800,Loss: 1.1972692012786865
训练次数:6900,Loss: 1.036494255065918
训练次数:7000,Loss: 0.8717333674430847
整体测试集上的Loss:1268.876708984375
整体测试集上的正确率:0.545799970626831
模型已保存
------第 10 轮训练开始------
训练次数:7100,Loss: 1.1975603103637695
训练次数:7200,Loss: 0.9316152930259705
训练次数:7300,Loss: 1.1453012228012085
训练次数:7400,Loss: 0.8225905299186707
训练次数:7500,Loss: 1.2555389404296875
训练次数:7600,Loss: 1.247175931930542
训练次数:7700,Loss: 0.842944324016571
训练次数:7800,Loss: 1.1817939281463623
整体测试集上的Loss:1218.3699951171875
整体测试集上的正确率:0.5684000253677368
模型已保存
"""

04.GPU训练模型
-
将网络模型,数据(输入,标注),损失函数都转换为
cuda()
型#由之前的代码 king = king.cuda() loss_fn = loss_fn.cuda() imgs = imgs.cuda() targets = targets.cuda()
-
更好的写法
if torch.cuda.is_available(): king = king.cuda()
05.完整的模型验证套路
import torchvision
from PIL import Image
image_path = "pytorch数据/images/dog.png"
image = Image.open(image_path)
print(image)
transform = torchvision.transforms.Compose(
[torchvision.transforms.Resize((32,32)),
torchvision.transforms.ToTensor()])
image = transform(image)
print(image.shape)
import torch
from torch import nn
class King(nn.Module):
def __init__(self):
super(King, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, 5, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 32, 5, padding=2),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, 5, padding=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("king_9.pth") #导入训练后的模型
print(model)
image = torch.reshape(image,(1,3,32,32))
model.eval()
with torch.no_grad():#可以节约一些性能
output = model(image)
print(output)
print(output.argmax(1))
"""
output:
torch.Size([3, 32, 32])
King(
(model): Sequential(
(0): Conv2d(3, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(2): Conv2d(32, 32, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(4): Conv2d(32, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
(5): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
(6): Flatten(start_dim=1, end_dim=-1)
(7): Linear(in_features=1024, out_features=64, bias=True)
(8): Linear(in_features=64, out_features=10, bias=True)
)
)
tensor([[ 1.3082, -8.2826, 4.5064, 2.6434, 2.8140, 3.6594, -2.4351, 0.3129,
-1.6153, -5.1815]])
tensor([2])
"""
- 虽然上面预测错了,但毕竟模型训练的次数太少,多训练几次,正确率会提高,像我运行了30次就成功了