4875 字
24 分钟
PyTorch 小土堆
TIP

PDF & Code & Data
https://pan.baidu.com/s/1_GHGfJQIs8iOEdiKqaYq5g?pwd=4533

第01章 配置🎈#

1.1 基本环境#

1.1.1 Anaconda#

采用最新版本Anaconda探索适应问题,安装时记好自己的 安装路径

Anaconda链接
当前https://www.anaconda.com/download/success
历史https://repo.anaconda.com

1.1.2 显卡驱动#

右键开始菜单 ->「任务管理器」->「性能」 以查看GPU信息
如有Nvidia的显卡,且正常显示型号,则显卡驱动无问题

1.1.3 环境管理#

也许,你之后会遇到不同的项目,需要使用到不同版本的环境。
比如这个项目要用到pytorch 0.4,另一个项目要用到pytorch 1.0。
如果你卸载0.4版本,安装1.0版本,那么下次你再碰到0.4版本,你就需要卸载1.0版本,安装0.4版本。

Anaconda集成的conda包就能够解决这个问题。
它可以创造出两个屋子相互隔离,一个屋子放0.4版本,一个屋子放1.0版本。
你需要哪个版本,就进哪个屋子工作。

# 启动Anaconda Prompt后,我们首先使用conda指令创建一个屋子,叫做pytorch
conda create -n pytorch python=3.6
# conda      指调用conda包
# create     创建的意思              
# -n         指后面的名字            
# pytorch    即名字(可以更改成自己喜欢的)   
# python=3.6 指创建的屋子为python3.6版本 

# 激活「pytorch」这个屋子
conda activate pytorch

# 查看「pytorch」中的包列表,目前还无pytorch包,需要安装
pip list

1.1.4 PyTorch#

本机无显卡选择CPU版本,选择后复制相应指令

PyTorch链接
当前https://pytorch.org/
Dochttps://pytorch.org/docs/stable/index.html
# 视频版本-CPU (本机选择)
conda install pytorch torchvision cpuonly -c pytorch
# 测试PyTorch
(pytorch) C:\Users\morinha>pip list
Package           Version
----------------- ---------
···
torch             1.10.2
torchvision       0.11.3
typing_extensions 4.1.1
wheel             0.37.1
wincertstore      0.2

(pytorch) C:\Users\morinha>python
···
>>> import torch
>>> torch.cuda.is_available()
False

1.2 基本工具#

1.2.1 PyCharm#

本文安装最新的 PyCharm 专业版 https://www.jetbrains.com/pycharm/download

新建项目 :「自定义环境」->「选择现有」->「Conda」->「conda.exe路径」-> 选择创建的pytorch

# Python 控制台 可以输入指令检测是否成功导入Conda的环境
import torch
torch.cuda.is_available()

1.2.2 Jupyter#

# 进入「pytorch」环境
conda activate pytorch
# 通过这个指令就可以安装好Jupyter
conda install nb_conda

# 在pytorch中启动Jupyter
jupyter notebook

新建项目:「New」->「conda的pytorch环境」

# Jupyter Notebook 测试
import torch
torch.cuda.is_available()

1.2.3 两个函数#

TIP
  • 查看帮助:PyCharm中使用 「ctrl+鼠标移动」 Jupyter中使用 「Name+??」
  • 查看参数:PyCharm中使用 「ctrl+P」
函数功能
dir( )能让我们知道工具箱、工具箱中的分隔区有什么东西
help( )能让我们知道每个工具是如何使用

# 导入 torch
import torch

# dir()
dir(torch)
dir(torch.cuda)
dir(torch.cuda.is_available) 

# help()
help(torch.cuda.is_available)

1.2.4 工具对比#

第02章 数据🎈#

2.1 加载数据#

2.1.1 Dataset#

PyTorch有关加载数据的,主要涉及 DatasetDataLoader
前者主要告诉后者如何获取数据,后者主要用于加载数据和为网络提供数据

# 导入Dataset类 
from torch.utils.data import Dataset

"""
utils 即工具的意思,从torch这个大工具箱中挑选出的实用工具区
data  从这个工具区中挑选出的数据有关工具

前面已经介绍了如何查看帮助,可以查到Dataset的用法
①应该继承 `Dataset` 这个抽象类
②应该重写 `__getitem__` 和 `__len__` 方法
"""
from torch.utils.data import Dataset  
from PIL import Image  
import os  
  
# 自己封装的 MyData类  
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 = "dataset/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)  
  
# 第1种方式  
print(ants_dataset[0])  
  
# 第2种方式(根据上面返回的提示而修改)  
img, label = ants_dataset[0]  
img.show()  
  
# 测试两个数据集的拼接 未改变顺序,ants在前 bees在后  
train_dataset = ants_dataset + bees_dataset  
print(len(ants_dataset))  
print(len(bees_dataset))  
print(len(train_dataset))  
  
img, label = train_dataset[123]  
img.show()  
img, label = train_dataset[124]  
img.show()

2.1.2 TensorBoard#

我们不知道一个神经网络执行具体细节是什么,要人工调试十分困难
TensorBoard 可以将程序的执行步骤都显示出来,对训练的参数(如损失值)统计并以图展现

# PyCharm中终端不显示「pytorch」
# 解决办法:「设置」->「工具 - 终端」-> 把 「Shell 路径」 修改为 「cmd.exe」、

# 安装TensoBoard(记得关闭梯子)
pip install tensorboard
# 导入SummaryWriter类 
from torch.utils.tensorboard import SummaryWriter  

"""
SummaryWriter类
	创建一个事件文件,在给定的目录中添加摘要和事件
		参数1 存放日志的文件夹名
		
本节视频只用到领个方法
	add_image() 在事件文件中添加图片
	add_scalar() 在TensorBoard中添加标量数据
		该方法可以用来添加训练过程中的损失值、准确率等指标,以便于在TensorBoard中进行可视化和比较
"""
# 查看日志的命令
# logdir=事件文件所在的 文件夹名
tensorboard --logdir=logs

# 可以修改端口
tensorboard --logdir=logs --port=6007

接下来使用一下「tensorboard」,同时查看生成的事件文件

from torch.utils.tensorboard import SummaryWriter  
  
writer = SummaryWriter("logs")  
  
# y = 2x  
# 同一个图像标题下,重复修改y值会导致,新图会包含之前的旧图  
for i in range(100):  
    writer.add_scalar("y = 2x", 2*i, i)  
writer.close()
from torch.utils.tensorboard import SummaryWriter  
from PIL import Image  
import numpy as np  
  
writer = SummaryWriter("logs")  
  
# 第1步  
# image_path = "data/train/ants_image/0013035.jpg"  
# 第2步  
image_path = "data/train/bees_image/16838648_415acd9e3f.jpg"  
  
img_PIL = Image.open(image_path)  
img_array = np.array(img_PIL)  
print(type(img_array))  
print(img_array.shape)  
  
# 从PIL到Numpy,需要在add_image()中指定图像的每一维  
writer.add_image("test", img_array, global_step=2, dataformats="HWC")  
  
writer.close()

2.2 转换数据#

2.2.1 Transforms#

Transforms 主要是用于图像变换的操作,可以对图像进行裁剪、标准化等
其包括很多常用的图像处理方法,比如transforms.ToTensor()

NOTE

为什么用Tensor数据类型?
Tensor类型中的很多属性我们都需要在神经网络中用到,如反向传播、梯度等

from PIL import Image  
from torch.utils.tensorboard import SummaryWriter  
from torchvision import transforms  
  
img_path = "data/train/ants_image/0013035.jpg"  
img = Image.open(img_path)  
print(img)  
  
# 使用transforms  
"""  
ToTensor() 可传入`PIL Image` 和 `numpy.ndarray` 两种图片格式  
    PIL Image:即用PIL的Image工具打开图像的格式  
    numpy.ndarray:即用OpenCV打开图像的格式(所以一般用这种方式打开,不用再转换图像了)  
"""  
tensor_trans = transforms.ToTensor()  
tensor_img = tensor_trans(img)  
print(tensor_img)  
  
writer = SummaryWriter("logs")  
writer.add_image("Tensor_img", tensor_img, 0)  
writer.close()

「OpenCV加载图像」是更为常用的方式,这里来测试一下

# 安装opencv(记得关闭梯子)
pip install opencv-python
# 测试opencv加载图像的格式
import cv2
cv_img = cv2.imread(img_path)
NOTE

多关注 「输入、输出」 类型,不会的多看 「官方文档」
关注方法需要的 「参数」 ,不知道返回值的时候 「Print」

# __call__ 类似「类的有参构造」,可看作为「对象-有参构造」
# 测试
class Person:  
    def __call__(self, name):  
        print("__call__"+" Hello "+name)  
  
    def hello(self, name):  
        print("Hello "+name)  
  
person = Person()  
person("zhangsan")

person.hello("lisi")
from PIL import Image  
from torch.utils.tensorboard import SummaryWriter  
from torchvision import transforms  
  
writer = SummaryWriter("logs")  
img = Image.open("images/0013035.jpg")  
# 打印后得知,图像为RGB三通道  
print(img)  
  
# 01 transforms.ToTensor()  
trans_totensor = transforms.ToTensor()  
img_tensor = trans_totensor(img)  
print(img_tensor[0][0][0])  
writer.add_image("ToTensor", img_tensor, 0)  
  
# 02 transforms.Normalize()  
# output[channel] = (input[channel] - mean[channel]) / std[channel]  
trans_norm = transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])  
img_norm = trans_norm(img_tensor)  
print(img_norm[0][0][0])  
writer.add_image("Normalize", img_norm, 0)  
  
# 03 Resize  
# 图像为PIL,经过Resize后,仍为PIL  
trans_resize = transforms.Resize((512, 512))  
img_resize = trans_resize(img)  
# 将PIL转为Tensor  
img_resize = trans_totensor(img_resize)  
writer.add_image("Resize", img_resize, 0)  
  
# 04 Compose 将几个转换组合在一起,先resize,再tensor  
trans_resize_2 = transforms.Resize(512)  
trans_compose = transforms.Compose([trans_resize_2, trans_totensor])  
img_resize_2 = trans_compose(img)  
writer.add_image("Resize", img_resize_2, 0)  
  
# 05 RandomCrop  
trans_random = transforms.RandomCrop(512)  
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)  
  
writer.close()

接下来使用一下torchvision中的数据集「CIFAR10」

import torchvision  
from torch.utils.tensorboard import SummaryWriter  
from urllib3.filepost import writer  
  
dataset_transforms = torchvision.transforms.Compose([  
    torchvision.transforms.ToTensor()  
])  
train_set = torchvision.datasets.CIFAR10(root='./dataset',
										train=True,
										transform=dataset_transforms,
										download=True)  
test_set = torchvision.datasets.CIFAR10(root='./dataset',
										train=False,
										transform=dataset_transforms,
										download=True)  
  
# 01 查看一下数据信息  
# (<PIL.Image.Image image mode=RGB size=32x32 at 0x23653E11F60>, 3)  
print(test_set[0])  
img, target = test_set[0]  
print(img)  
print(target)  
  
print(test_set.classes)  
print(test_set.classes[target]) # cat  
img.show()  
  
# 02 通过tensorboard查看  
writer = SummaryWriter("logs")  
  
for i in range(10):  
    img, target = train_set[i]  
    writer.add_image("test_set", img, i)  
  
writer.close()

2.2.2 DataLoader#

import torchvision  
from torch.utils.data import DataLoader  
from torch.utils.tensorboard import SummaryWriter  
  
test_data = torchvision.datasets.CIFAR10("./dataset", 
										train=False,
										transform=torchvision.transforms.ToTensor(),
										download=True)  
test_loader = DataLoader(test_data,batch_size=64,shuffle=True,
                                                num_workers=0,
                                                drop_last=True)  
  
# 测试数据集中第一张图片  
img, target = test_data[0]  
print(img.shape)  
print(target)  
  
writer = SummaryWriter("dataloader")  
for epoch in range(2):  
    step = 0  
    for data in test_loader:  
        imgs, targets = data  
        # print(imgs.shape)  
        # print(targets)        
        writer.add_images("Epoch:{}".format(epoch),imgs,step)  
        step = step + 1  
  
writer.close()

第03章 神经网络🎈#

3.1 基本骨架#

3.1.1 Module#

torch.nn是非常常用的包,其中torch.container中的 「Module」 是所有神经网络的基类
自己定义的模型需要实现「Module」的__init__forward函数

import torch  
from torch import nn  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super().__init__()  
  
    def forward(self, input):  
        output = input + 1  
        return output  
  
tudui = Tudui()  
x = torch.tensor([1.0])  
output = tudui(x)  
print(output)

3.1.2 卷积层#

卷积层中最常使用 Conv2d
卷积核的 「size」 一般是预定义好的
卷积核的 「weights」 一般是先初始化然后通过不断迭代学习而来的

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]])  
# 01 看尺寸发现不满足,修改一下尺寸  
print(input.shape)  
print(kernel.shape)  
  
input = torch.reshape(input,(1,1,5,5))  
kernel = torch.reshape(kernel,(1,1,3,3))  
  
## 02 卷积 conv2d
output = F.conv2d(input,kernel,stride=1)  
print(output)  
  
output2 = F.conv2d(input,kernel,stride=2)  
print(output2)  
  
output3 = F.conv2d(input,kernel,stride=1,padding=1)  
print(output3)

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  
from urllib3.filepost import writer  
  
datset = torchvision.datasets.CIFAR10("data",  
                                      train=False,  
                                      transform=torchvision.transforms.ToTensor(),  
                                      download=True)  
  
dataloader = DataLoader(datset,batch_size=64)  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.conv1 = nn.Conv2d(in_channels=3,out_channels=6,
                                                kernel_size=3,
                                                stride=1,padding=0)  
  
    def forward(self,x):  
        x = self.conv1(x)  
        return x  
  
tudui = Tudui()  
# print(tudui)  
  
writer = SummaryWriter("logs")  
step = 0  
for data in dataloader:  
    imgs, targets = data  
    output = tudui(imgs)  
    # print(imgs.shape)  
    # print(output.shape)  
    writer.add_images("input",imgs,step) # torch.Size([64, 3, 32, 32])  
    output = torch.reshape(output,(-1,3,30,30))  
    writer.add_images("output",output,step) # torch.Size([64, 6, 30, 30])  
    step = step + 1
  
writer.close()

3.1.3 池化层#

池化层中最常使用 「MaxPool2d」
其一是对卷积层所提取的信息做更一步降维,减少计算量
其二是加强图像特征的不变性,使之增加图像的偏移、旋转等方面的鲁棒性

import torch  
import torchvision  
from torch import nn  
from torch.utils.data import DataLoader  
from torch.utils.tensorboard import SummaryWriter  
  
# 01 测试最大池化  
# 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 Tudui(nn.Module):  
#     def __init__(self):  
#         super().__init__()  
#         self.maxpool1 = nn.MaxPool2d(kernel_size=3,ceil_mode=False)  
#  
#     def forward(self, input):  
#         output = self.maxpool1(input)  
#         return output  
#  
# tudui = Tudui()  
# output = tudui(input)  
# print(output)  
   
# 02 针对数据集测试  
dataset = torchvision.datasets.CIFAR10(root='./data',  
                                       train=False,  
                                       transform=torchvision.transforms.ToTensor(),  
                                       download=True)  
dataloader = DataLoader(dataset,batch_size=64)  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.maxpool1 = nn.MaxPool2d(kernel_size=3,ceil_mode=False)  
  
    def forward(self, input):  
        output = self.maxpool1(input)  
        return output  
  
tudui = Tudui()  
  
writer = SummaryWriter("logs")  
step = 0  
for data in dataloader:  
    imgs, targets = data  
    writer.add_images("input",imgs,step)  
    output = tudui(imgs)  
  
    writer.add_images("output",output,step)  
    step = step + 1  
  
writer.close()

3.1.4 激活层#

激活层一般采用 「非线性激活」,神经网络中引入非线性的特质,才能训练出符合各种特征的模型
非线性激活函数有很多,如比较常见 「ReLU」「Sigmoid」

import torch  
import torchvision  
from torch import nn  
from torch.nn import ReLU  
from torch.utils.data import DataLoader  
from torch.utils.tensorboard import SummaryWriter  
  
from nn_conv2d import dataloader  
  
# # 01 测试ReLU  
# input = torch.tensor([[1,-0.5],  
#                       [-1,3]])  
#  
# output = torch.reshape(input,(-1,1,2,2))  
# print(output.shape)  
#  
# class Tudui(nn.Module):  
#     def __init__(self):  
#         super().__init__()  
#         self.relu1 = nn.ReLU()  
#  
#     def forward(self, input):  
#         output = self.relu1(input)  
#         return output  
#  
# tudui = Tudui()  
# output = tudui(input)  
# print(output)  
  
# 02 Sigmoid  
dataset = torchvision.datasets.CIFAR10(root='./data',  
                                       train=False,  
                                       transform=torchvision.transforms.ToTensor(),  
                                       download=True)  
  
dataloader = DataLoader(dataset,batch_size=64)  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.relu1 = nn.ReLU()  
        self.sigmoid1 = nn.Sigmoid()  
  
    def forward(self, input):  
        output = self.sigmoid1(input)  
        return output  
  
tudui = Tudui()  
  
writer = SummaryWriter("logs")  
  
step = 0  
for data in dataloader:  
    imgs, targets = data  
    writer.add_images("input",imgs,step)  
    output = tudui(imgs)  
    writer.add_images("output",output,step)  
    step += 1  
  
writer.close()

3.1.5 其他结构#

「Transformer Layers」 特定的网络结构
「Sparse Layers」 特定的网络结构,其中的 Embedding 用于自然语言处理

「Linear Layers」 用的较多,即 全连接层

import torch  
import torchvision  
from torch import nn  
from torch.nn import Linear  
from torch.utils.data import DataLoader  
  
dataset = torchvision.datasets.CIFAR10("./data",  
                                       train=False,  
                                       transform=torchvision.transforms.ToTensor(),  
                                       download=True)  
  
dataloader = DataLoader(dataset, batch_size=64)  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super().__init__()  
        self.linear1 = Linear(196608, 10)  
  
    def forward(self, input):  
        output = self.linear1(input)  
        return output  
  
tudui = Tudui()  
  
for data in dataloader:  
    imgs, targets = data  
    print(imgs.shape) # torch.Size([64, 3, 32, 32])  
  
    # flatten 展平成一行  
    output = torch.flatten(imgs)  
    print(output.shape) # torch.Size([196608])  
  
    output = tudui(output)  
    print(output.shape)

3.1.6 Sequential#

Sequential 主要是方便代码的编写,使代码更加简洁
根据下图搭建神经网络:判断一个图的类别(最后输出为十个类别,最后进行判断)

import torch  
from torch import nn  
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential  
from torch.utils.tensorboard import SummaryWriter  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super(Tudui, self).__init__()  
        self.model1 = 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.model1(x)  
        return x  
  
tudui = Tudui()  
print(tudui)  
  
# 检查网络  
input = torch.ones((64, 3, 32, 32))  
output = tudui(input)  
print(output.shape)  
  
writer = SummaryWriter("logs")  
# tensorboard中的add_graph 查看神经网络的流程图  
writer.add_graph(tudui, input)  
writer.close()

3.2 基本优化#

3.2.1 损失函数#

损失函数(Loss Function)用于衡量模型的「预测输出」与「实际标签」之间的差异或者误差
损失越小越好,根据loss调整参数,以减小损失

# 计算output和target之差的「绝对值」
nn.L1Loss()

# 计算output和target之差的「均方差」
nn.MSELoss()

# 交叉熵损失函数(Cross-Entropy Loss Function)是在分类问题中经常使用的一种损失函数
# 当分类预测正确时,损失要比较小,即`-x[class]`相比右边的项应较大
nn.CrossEntropyLoss() 

import torch  
from torch.nn import L1Loss  
from torch import nn  
  
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))  
  
# 01 L1Loss  
loss = L1Loss(reduction='sum') # mean or sum  
result = loss(inputs, targets)  
print(result)  
  
# 02 MSELoss  
loss_mse = nn.MSELoss()  
result_mse = loss_mse(inputs, targets)  
print(result_mse)  
  
# 03 CrossEntropyLoss  
x = torch.tensor([0.1, 0.2, 0.3])  
y = torch.tensor([1])  
x = torch.reshape(x, (1, 3))  
  
loss_cross = nn.CrossEntropyLoss()  
result_cross = loss_cross(x, y)  
print(result_cross)

3.2.2 反向传播#

前向传播:将训练集数据输入到NN的「输入层」,经过「隐藏层」,最后到达「输出层」并输出结果
反向传播:计算估计值与实际值之间的误差,并将误差从「输出层」向「隐藏层」反向传播,直至「输入层」

import torchvision  
from torch import nn  
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear  
from torch.utils.data import DataLoader  
  
dataset = torchvision.datasets.CIFAR10("./data",  
                                       train=False,  
                                       transform=torchvision.transforms.ToTensor(),  
                                       download=True)  
  
dataloader = DataLoader(dataset, batch_size=1)  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super(Tudui, self).__init__()  
        self.model1 = 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.model1(x)  
        return x  
  
loss = nn.CrossEntropyLoss()  
tudui = Tudui()  
for data in dataloader:  
    imgs, targets = data  
    outputs = tudui(imgs)  
    result_loss = loss(outputs, targets)  
    # 反向传播 计算梯度  
    result_loss.backward()  
    print("ok")

3.2.3 优化器#

NOTE

更新参数 减少损失
反向传播(backward)–> 计算出梯度(grad)–> 根据梯度和学习率更新参数 –> 减小loss

优化器的种类比较多,常用的就是SGD(随机梯度下降)
不同优化器的参数列表不同,一般设置「parameters」和l「lr」这两个参数,其他默认

import torch  
import torchvision  
from torch import nn  
from torch.nn import Sequential, Conv2d, MaxPool2d, Flatten, Linear  
from torch.optim.lr_scheduler import StepLR  
from torch.utils.data import DataLoader  
  
dataset = torchvision.datasets.CIFAR10("./data",  
                                       train=False,  
                                       transform=torchvision.transforms.ToTensor(),  
                                       download=True)  
  
dataloader = DataLoader(dataset, batch_size=1)  
  
class Tudui(nn.Module):  
    def __init__(self):  
        super(Tudui, self).__init__()  
        self.model1 = 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.model1(x)  
        return x  
  
loss = nn.CrossEntropyLoss()  
tudui = Tudui()  
  
# 优化器 SGD(随机梯度下降)  
optim = torch.optim.SGD(tudui.parameters(), lr=0.01) # lr学习率,太大不稳定,太小收敛慢  
for epoch in range(20):  
    running_loss = 0.0  
    for data in dataloader:  
        imgs, targets = data  
        outputs = tudui(imgs)  
        result_loss = loss(outputs, targets)  
  
        optim.zero_grad() # 把上一步的梯度清零,否则会累加  
        result_loss.backward()  
        optim.step() # 对weight参数进行更新  
  
        running_loss = running_loss + result_loss  
    print(running_loss)

第04章 炼丹流程🎈#

4.1 模型操作#

4.1.1 使用&修改#

torchvision.models有分类、语义分割、目标检测等,本次讲分类模型VGG
最常用的为VGG16和VGG19,VGG16的数据集 ImageNet 需安装「scipy」

# 安装scipy(记得关闭梯子)
pip install scipy
import torchvision  
from torch import nn  
  
# VGG16 对应的 ImageNet数据集 由于数据集过大选择放弃  
# train_data = torchvision.datasets.ImageNet("./data_image_net",  
#                                            split='train',  
#                                            download=True,  
#                                            transform=torchvision.transforms.ToTensor())  
  
# 01 使用  
vgg16_false = torchvision.models.vgg16(pretrained=False) # 初始参数  
vgg16_true = torchvision.models.vgg16(pretrained=True)   # 训练好的  
  
print(vgg16_true)  
  
train_data = torchvision.datasets.CIFAR10('./data',  
                                          train=True,  
                                          transform=torchvision.transforms.ToTensor(),  
                                          download=True)  
  
# 02 修改方式  
# (1) 加线性层  
print(vgg16_true)  
vgg16_true.classifier.add_module('add_linear', nn.Linear(1000, 10))  
print(vgg16_true)  
  
# (2) 直接修改  
print(vgg16_false)  
vgg16_false.classifier[6] = nn.Linear(4096, 10)  
print(vgg16_false)

4.1.2 保存&加载#

训练好的模型需要进行保存,以备后续加载使用
自定义模型保存后加载,记得要 「导入」

import torch  
import torchvision  
from torch import nn  
  
vgg16 = torchvision.models.vgg16(pretrained=False)  
  
  
# 01 保存方式  
# (1) 模型参数 + 模型结构  
torch.save(vgg16, "vgg16_method1.pth")  
  
# (2) 模型参数(官方推荐)  
torch.save(vgg16.state_dict(), "vgg16_method2.pth")  
  
# 02 自定义模型  
class Tudui(nn.Module):  
    def __init__(self):  
        super(Tudui, self).__init__()  
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3)  
  
    def forward(self, x):  
        x = self.conv1(x)  
        return x  
  
tudui = Tudui()  
torch.save(tudui, "tudui_method1.pth")
import torch  
import torchvision  
  
# 01 加载方式  
# (1) 加载 模型参数 + 模型结构  
model = torch.load("vgg16_method1.pth")  
print(model)  
  
# (2) 加载 模型参数  
vgg16 = torchvision.models.vgg16(pretrained=False)  
vgg16.load_state_dict(torch.load("vgg16_method2.pth"))  
print(vgg16)  
  
# 02 自定义模型 加载陷阱  
# 需要复制过来 or 导入  
from model_save import *  
model = torch.load('tudui_method1.pth')  
print(model)

4.2 模型训练#

import torchvision  
from torch.utils.data import DataLoader  
from torch.utils.tensorboard import SummaryWriter  
from model import *  
  
# 01 准备数据集  
train_data = torchvision.datasets.CIFAR10(root="./data",  
                                          train=True,  
                                          transform=torchvision.transforms.ToTensor(),  
                                          download=True)  
test_data = torchvision.datasets.CIFAR10(root="./data",  
                                         train=False,  
                                         transform=torchvision.transforms.ToTensor(),  
                                         download=True)  
# length 长度  
train_data_size = len(train_data)  
test_data_size = len(test_data)  
print("训练数据集的长度为:{}".format(train_data_size))  
print("测试数据集的长度为:{}".format(test_data_size))  
  
# 02 加载数据集 
DataLoadertrain_dataloader = DataLoader(train_data, batch_size=64)  
test_dataloader = DataLoader(test_data, batch_size=64)  
  
# 03 创建网络模型  
tudui = Tudui()  
  
# 04 损失函数  
loss_fn = nn.CrossEntropyLoss()  
  
# 05 优化器  
learning_rate = 1e-2 # 0.01  
optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)  
  
# 06 设置训练网络的一些参数  
total_train_step = 0 # 记录训练的次数  
total_test_step = 0 # 记录测试的次数  
epoch = 10 # 训练的轮数  
  
# 添加tensorboard  
writer = SummaryWriter("logs")  
for i in range(epoch):  
    print("-------第 {} 轮训练开始-------".format(i+1))  
  
    """  
        仅对一部分网络层有作用, 如Dropout、BatchNorm层  
        tudui.train()   设置成训练模式(可选)  
        tudui.eval()    设置成测试模式(可选)  
    """    
    # 07 训练步骤开始  
    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)  
  
    # 08 测试步骤开始  
    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)  
            total_test_loss = total_test_loss + loss.item()  
  
            accuracy = (outputs.argmax(1) == targets).sum() # argmax -> 正确个数  
            total_accuracy = total_accuracy + accuracy  
  
    print("整体测试集上的Loss: {}".format(total_test_loss))  
    print("整体测试集上的正确率: {}".format(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 = total_test_step + 1  
  
    torch.save(tudui, "tudui_{}.pth".format(i))  
    print("模型已保存")  
  
writer.close()
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(64*4*4, 64),  
            nn.Linear(64, 10)  
        )  
  
    def forward(self, x):  
        x = self.model(x)  
        return x  
  
# main函数  
if __name__ == '__main__':  
  
    tudui = Tudui()  
    input = torch.ones((64, 3, 32, 32))  
    output = tudui(input)  
    print(output.shape)

正确率是分类问题比较特有的指标,计算正确率的方法如下

import torch  
  
# item()函数  
# 从只包含单个元素的张量中提取Python数值,将张量转换为标量值  
a = torch.tensor(5)  
print(a)  
print(a.item())  
  
# argmax()函数  
# 0纵向,1横向  
outputs = torch.tensor([[0.1,0.2],  
                        [0.05,0.4]])  
print(outputs.argmax(0)) # 纵向对比  
print(outputs.argmax(1)) # 横向对比

4.3 GPU#

import torch  
import torchvision  
from torch import nn, cuda  
from torch.utils.data import DataLoader  
from torch.utils.tensorboard import SummaryWriter  
  
# 01 准备数据集

# ···
# ···
  
# 03 创建网络模型  
"""  
使用GPU要做的修改  
    01 网络模型  
    02 数据  
    03 损失函数  
"""  
tudui = Tudui()  
if cuda.is_available():  
    tudui.cuda()  
  
# 04 损失函数  
loss_fn = nn.CrossEntropyLoss()  
if cuda.is_available():  
    loss_fn.cuda()  
# ···
# ···
    # 07 训练步骤开始   
    for data in train_dataloader:  
        imgs, targets = data  
  
        if cuda.is_available():  
            imgs = imgs.cuda()  
            targets = targets.cuda()  
# ···
# ···
    # 08 测试步骤开始  
        for data in test_dataloader:  
            imgs, targets = data  
  
            if cuda.is_available():  
                imgs = imgs.cuda()  
                targets = targets.cuda()  
import torch  
import torchvision  
from torch import nn, cuda  
from torch.utils.data import DataLoader  
from torch.utils.tensorboard import SummaryWriter  
  
"""  
单显卡  
Torch.device(“cuda”)  
Torch.device(“cuda:0”)  
  
多显卡-第2张  
Torch.device(“cuda:1”)  
"""  
# 定义训练的设备  
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  
  
# 01 准备数据集  

# ···
# ···

# 03 创建网络模型  
"""  
使用GPU要做的修改  
    01 网络模型  
    02 数据  
    03 损失函数  
"""  
tudui = Tudui()  
tudui.to(device)  
  
# 04 损失函数  
loss_fn = nn.CrossEntropyLoss()  
loss_fn.to(device)  
# ···
# ···
    # 07 训练步骤开始  
    for data in train_dataloader:  
        imgs, targets = data  
  
        imgs = imgs.to(device)  
        targets = targets.to(device)  
# ···
# ···
    # 08 测试步骤开始  
        for data in test_dataloader:  
            imgs, targets = data  
  
            imgs = imgs.to(device)  
            targets = targets.to(device)  

4.4 验证#

TIP

本节加载模型,选择导入的方式,不再复制代码

import torch  
import torchvision  
from PIL import Image  
from torch import nn  
from model import *  
  
image_path = "./imgs/dog.png"  
image = Image.open(image_path)  
"""  
png格式是RGBA四个通道,需要转为RGB颜色通道  
视频可以运行是因为不同截图软件保留的通道数是不一样的  
"""  
image = image.convert('RGB')  
transform = torchvision.transforms.Compose([  
    torchvision.transforms.Resize((32, 32)),  
    torchvision.transforms.ToTensor()  
])  
image = transform(image)  
print(image.shape)  
  
# # 01 CPU环境  
# model = torch.load("tudui_0.pth")  
  
# 02 CPU环境 验证 GPU环境下训练的模型  
model = torch.load("tudui_29_gpu.pth",map_location=torch.device('cpu'))  
  
image = torch.reshape(image, (1, 3, 32, 32))  
model.eval()  
with torch.no_grad():  
    output = model(image)  
print(output)  
print(output.argmax(1)) # 横向
PyTorch 小土堆
https://www.morinha.cc/posts/courses/pytorch-小土堆/
作者
东山吃鱼
发布于
2025-02-24
许可协议
CC BY-NC-SA 4.0