前言
简介: PyTorch是一个基于Python的科学计算库,它主要提供了两个高级功能:一是支持张量计算,类似于NumPy,但是可以在GPU上运行;二是支持构建和训练深度神经网络。
在PyTorch中,张量(Tensor)是最基本的数据结构,类似于NumPy中的多维数组,但是可以在GPU上进行高效的计算。PyTorch支持多种张量操作,包括张量加法、减法、乘法、除法、矩阵乘法等,同时还支持各种元素级函数和聚合函数。
PyTorch还提供了一种灵活的、动态的计算图机制,称为Autograd。在PyTorch中,每个张量都可以被看作是计算图中的一个节点,每个节点都有一个对应的梯度节点,用于计算该节点对应的变量的梯度。当我们对计算图中的某个节点进行操作时,PyTorch会自动构建该节点的梯度计算图,并通过反向传播算法计算出该节点对应的变量的梯度。
PyTorch还提供了一种方便的、高层次的API,用于构建和训练深度神经网络。这个API称为torch.nn模块,它提供了各种层和损失函数的实现,可以方便地构建各种类型的深度神经网络,例如卷积神经网络、循环神经网络、自编码器等。同时,PyTorch还提供了torch.optim模块,用于实现各种优化算法,例如SGD、Adam等,以便训练深度神经网络。
总之,PyTorch是一个功能强大、易于使用的深度学习框架,适用于各种类型的深度学习任务,包括图像处理、自然语言处理、语音识别等。同时,PyTorch还具有优秀的可扩展性和灵活性,可以方便地与其他Python库和工具集成使用。
视频链接: PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】
1 Dataset类
from torch.utils.data import Dataset
from PIL import Image
import os
class MyData(Dataset):
def __init__(self, root_dir, label_dir):
self.root_dir = root_dir
self.label_dir = label_dir
# os.path.join():将两个路径用“\\”连接
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)
train_root_dir = "data/images/dataset/train"
ant_label_dir = "ants_image"
bee_label_dir = "bees_image"
ant_dataset = MyData(train_root_dir, ant_label_dir)
bee_dataset = MyData(train_root_dir, bee_label_dir)
# __getitem__() 调用方法
ant_img, ant_label = ant_dataset[0]
print(ant_dataset[0])
train_dataset = ant_dataset + bee_dataset # 按加的顺序组合
train_img, train_label = train_dataset[124]
print(len(ant_dataset), len(bee_dataset), sep="\n") # 124 121
train_img.show()
2 TensorBoard
- TensorBoard是一个用于可视化TensorFlow模型和训练结果的工具。它可以帮助你更好地理解和调试你的模型,比如可视化模型的结构、训练和评估指标、数据分布、嵌入向量、图像和视频等。通过使用TensorBoard,你可以更直观地了解模型的表现,找到模型中的问题并进行优化。同时,它也可以用来比较不同模型的性能,跟踪训练过程中的进展,并与他人分享你的研究成果。TensorBoard是一个非常有用的工具,可以帮助你更高效地进行深度学习研究和开发。
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter("logs")
# writer.add_image()
for i in range(100):
# tag:标题;scalar_value:y轴;global_step:x轴
writer.add_scalar(tag="y = x", scalar_value=i * i, global_step=i)
writer.close()
运行上面代码之后,在下方终端输入:
tensorboard --logdir=logs
之后打开网页:
http://localhost:6006/
端口也可以自己改(6007可以随便改):
tensorboard --logdir=logs --port=6007
3 Transforms
- 在PyTorch中,Transforms是一种数据预处理的方式。它可以用来对输入数据进行各种变换,例如调整大小、裁剪、旋转、翻转、标准化等等。这些变换可以帮助我们增加训练数据的多样性,减少过拟合,提高模型的泛化能力。
from PIL import Image
from torch.utils.tensorboard import SummaryWriter
from torchvision import transforms
writer = SummaryWriter("logs")
img = Image.open("data/images/SSSS.Dynazenon.jpg")
'''
toTensor:
Convert a ``PIL Image`` or ``numpy.ndarray`` to tensor
PIL Image 和 numpy.ndarray 分别对应PIL和opencv的图片读取
'''
trans_toTensor = transforms.ToTensor()
img_tensor = trans_toTensor(img)
writer.add_image("ToTensor", img_tensor)
'''
normalize:归一化
计算方法:output[channel] = (input[channel] - mean[channel]) / std[channel]
防止数据集中有过大或者过小的值,影响训练
'''
trans_norm = transforms.Normalize([5, 0.5, 0.5], [0.5, 0.5, 0.5]) # 三个通道的均值和标准差
img_norm = trans_norm(img_tensor)
writer.add_image("norm img", img_norm)
'''
resize:
Resize the input image to the given size.
resize不会改变图片的数据类型
'''
trans_resize = transforms.Resize((512, 512))
img_resize = trans_resize(img_tensor)
writer.add_image("resize img", img_resize)
'''
compose:
compose就是一个指定一个transform操作序列,定义了一条加工流水线
'''
trans_resize_2 = transforms.Resize(512) # 将短边缩到 512
# 先 toTensor 再 resize,前一个的输出对应后一个输入
trans_compose = transforms.Compose([trans_toTensor, trans_resize_2])
img_resize_2 = trans_compose(img)
writer.add_image("resize_2 img", img_resize_2)
'''
random crop:
随机剪一个指定尺寸的新图片
'''
trans_random = transforms.RandomCrop(500) # 指定裁剪尺寸
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()
4 运用Datasets以及Transform
import torchvision
from torch.utils.tensorboard import SummaryWriter
# transforms操作序列
dataset_transform = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()
])
# 训练集测试集初始化
train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True, transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False, transform=dataset_transform, download=True)
# 将测试集前 10 张在 TensorBoard 显示
writer = SummaryWriter("logs2")
for i in range(10):
img, target = test_set[i]
writer.add_image("test_set", img, i)
writer.close()
5 DataLoader
-
在PyTorch中,DataLoader是一个用于加载和处理数据的工具。它可以帮助我们对数据进行批量读取、异步加载、并行处理等操作,从而提高训练的效率。
-
通常情况下,我们需要将数据集转化为一个Dataset对象,并通过DataLoader将其加载到模型中进行训练。Dataset对象可以是PyTorch内置的数据集,也可以是用户自定义的数据集。每个数据集可以包含多个样本,每个样本包含一个输入和一个标签。在DataLoader中,我们可以指定批量大小、数据加载顺序、多线程等参数,从而对数据进行处理和加载。
-
在训练时,我们可以通过迭代DataLoader对象来获取数据。每个迭代返回一个批量的数据,包含了输入和对应的标签。这样,我们就可以在模型上进行批量的训练和推理操作,从而提高训练的效率和准确率。
-
总之,DataLoader是PyTorch中一个非常重要的工具,可以帮助我们更加高效地加载和处理数据,从而使得模型训练更加简单和快速。
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
test = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor())
'''
batch size:loader能每次装弹4枚进入枪膛,或者理解每次抓四张牌
shuffle:每次epoch是否打乱原来的顺序,就像打完一轮牌后,洗不洗牌
num_workers:进程数
drop last:最后的打他不够一个 batch 还要不要了
'''
test_loader = DataLoader(dataset=test, batch_size=4, shuffle=True, num_workers=0, drop_last=False)
# 使用 board 可视化
writer = SummaryWriter("logs_dataloader")
step = 0
for data in test_loader:
imgs, targets = data
# 这里如果你用add image会报错,因为这个方法只能一次写一个图片,你必须换add images方法来写入带有批处理的图片
# writer.add_image("test set loader", imgs, step)
writer.add_images("test set loader", imgs, step)
step += 1
writer.close()
6 nn_module
- nn 是 Neural Network(神经网络)的缩写
import torch
import torch.nn as nn
# 神经网络类的构造
class Module(nn.Module):
def __init__(self):
super().__init__()
# nn.Module 中的 call 方法会调用 forward
def forward(self, input):
output = input + 1
return output
mdl = Module()
x = torch.tensor(1.0)
output = mdl(x)
print(output)
7 nn_conv(卷积)
-
conv 是 Convolution(卷积)的缩写
-
下面是一个简单的示意图,展示了一个 $3\times 3$ 的输入数据和一个 $2\times 2$ 的卷积核进行卷积操作的过程。
输入数据: 卷积核: 1 2 3 1 0 4 5 6 0 1 7 8 9 卷积操作后的输出: 6 8 12 14
我们将卷积核从输入数据的左上角开始扫描,并逐步向右下方移动。对于每一个位置,我们将卷积核对应的元素与输入数据中对应位置的元素相乘,并将所有乘积相加,得到一个标量值作为输出数据 $y$ 中对应位置的值。
例如,在扫描到输入数据的左上角时,卷积核与输入数据中对应位置的值进行乘法运算,得到的结果为:
1*1 + 0*2 + 0*4 + 1*5 = 6
具体的卷积操作可以用以下公式表示:
$$
y_{i,j}=\sum_{m=1}{M}\sum_{n=1}w_{m,n}x_{i+m-1,j+n-1}
$$
其中 $x$ 是输入数据,$w$ 是卷积核,$y$ 是输出数据,$M$ 和 $N$ 是卷积核的大小,$i$ 和 $j$ 是输出数据中的位置坐标。 -
卷积运算示意图:
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]])
# conv2d需要输入的tensor是四维的(batch, c,h,w),但是现在的input kernel是二维
# (要变换的矩阵, (批次大小(batch size), 通道数(channel), 矩阵高度, 矩阵宽度))
input = torch.reshape(input, (1, 1, 5, 5))
kernel = torch.reshape(kernel, (1, 1, 3, 3))
# stride:步长,padding:如果步长是 1,又想保持输入输出高宽不变,就把 padding 设置 1
output1 = F.conv2d(input, kernel, stride=1)
print(output1)
# padding=1 会在输入矩阵外圈填充一圈 0,在卷积时,如果 stride=1,输入输出尺寸会不变
output2 = F.conv2d(input, kernel, stride=1, padding=1)
print(output2)
8 nn_conv2d(卷积)
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, ReLU, Sigmoid, Linear, Flatten, Sequential
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", False, torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, batch_size=64)
class Module(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = Conv2d(in_channels=3, out_channels=6,
kernel_size=(3, 3), stride=(1, 1), padding=0)
def forward(self, x):
x = self.conv1(x)
return x
mdl = Module()
print(mdl)
writer = SummaryWriter("logs_nn_conv2d")
step = 0
for data in dataloader:
img, target = data
output = mdl(img)
# output 的 size 是 torch.Size([64, 6, 30, 30])
writer.add_images("before conv2d", img, step)
# output 是 6 通道,board不知道该怎么写入图片了,所以要 reshape
output = torch.reshape(output, (-1, 3, 30, 30)) # batch size不知道可以填 -1
writer.add_images("after conv2d", output, step)
step += 1
writer.close()
9 nn_maxpool(最大池化)
-
池化(Pooling)是神经网络中另一种常用的操作,通常紧随卷积层之后,用于减小特征图的尺寸,提高模型的计算效率和泛化能力。
-
最大池化(Max Pooling)是一种常用的池化操作,其原理是在输入数据的每个局部区域中选取最大的值作为输出。下面我来详细解释一下最大池化运算的过程,并举一个简单的例子来说明。
假设我们有一个 $4\times 4$ 的输入数据 $x$,如下所示:
1 2 2 1 2 2 1 1 1 1 1 2 2 1 2 1
我们希望对这个输入数据进行最大池化操作,设置池化窗口的大小为 $2\times 2$,即将输入数据分割成若干个 $2\times 2$ 的局部区域。对于每个局部区域,我们选取其中的最大值作为输出。
具体地,我们从输入数据的左上角开始,将池化窗口逐步向右下方移动。对于每个池化窗口,我们选取其中的最大值作为输出。例如,在扫描到输入数据的左上角时,池化窗口覆盖的局部区域为:
1 2 2 2
其中最大的值为 $2$,因此将 $2$ 作为输出。接着,将池化窗口向右移动池化窗口尺寸大小,重复上述操作,得到第一行第二个位置的输出:
2 1 1 1
其中最大的值为 $2$,因此将 $2$ 作为输出。以此类推,可以得到输入数据的所有局部区域的最大值,从而得到最终的输出数据 $y$:
2 2 2 2
这个输出数据的尺寸是原输入数据的一半,因为我们使用了 $2\times 2$ 的池化窗口,将输入数据的尺寸减小了一半。
-
最大池化操作可以减小特征图的尺寸,同时还可以提取输入数据的局部不变特征,增强模型对图像、语音等数据的抗干扰能力。在卷积神经网络中,通常将最大池化操作和卷积操作交替出现,构成卷积池化层组合,用于提取输入数据的高层特征。
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
# dtype:设置矩阵元素的数据类型
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.float)
input = torch.reshape(input, (-1, 1, 5, 5))
dataset = torchvision.datasets.CIFAR10("./dataset", train=False,
transform=torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset=dataset, batch_size=64)
# ceil mode:池化核走出input时还要不要里边的最大值 默认不要
class Module(nn.Module):
def __init__(self):
super(Module, self).__init__()
self.max_pool1 = MaxPool2d(kernel_size=(3, 3), ceil_mode=True)
def forward(self, input):
output = self.max_pool1(input)
return output
mdl = Module()
output = mdl(input)
print(output)
writer = SummaryWriter("logs_maxpool")
step = 0
for data in dataloader:
img, target = data
writer.add_images("input", img, step)
output = mdl(img)
writer.add_images("output", output, step)
step += 1
writer.close()
10 nn_relu(非线性激活)
-
非线性激活函数是神经网络中一种常用的激活函数,它的作用是将神经元的输入信号进行非线性变换,增强神经网络的非线性表示能力。常见的非线性激活函数包括sigmoid函数、tanh函数、ReLU函数、Leaky ReLU函数、ELU函数等。
其中,sigmoid函数将输入映射到[0,1]的范围内,tanh函数将输入映射到[-1,1]的范围内。ReLU函数在输入为正时输出等于输入,输入为负时输出为0,Leaky ReLU函数在输入为负时输出一个小的负数,ELU函数在输入为负时输出一个接近于0的小值,比ReLU函数更平滑。
在神经网络中,非线性激活函数的作用是引入非线性变换,使得神经网络可以学习非线性关系,从而提高模型的表达能力和性能。
-
ReLU(Rectified Linear Unit)函数是一种常用的非线性激活函数,它将输入信号直接输出作为输出信号,即:
f(x) = max(0, x)
其中,x是输入信号,f(x)是输出信号。
ReLU函数的特点是在输入为正时直接输出,而在输入为负时输出为0,因此它具有计算简单、速度快的优点。此外,ReLU函数还具有一定的生物学合理性,因为在生物神经元中,当输入信号超过一定阈值时才会产生输出信号,而ReLU函数也具有这种类似的特点。
在深度学习中,ReLU函数已经成为了一种标配的激活函数,并且在很多神经网络模型中都被广泛使用。它的应用除了可以提高模型的表达能力外,还可以缓解神经网络中的梯度消失问题,从而加速模型的训练和收敛。
-
Sigmoid函数是一种常用的非线性激活函数,它将输入信号压缩到[0,1]的范围内,具有平滑的S形曲线,定义如下:
f(x) = 1 / (1 + exp(-x))
其中,x是输入信号,f(x)是输出信号。
Sigmoid函数的输出值在[0,1]之间,并且随着输入值的变化呈现出平滑的曲线变化,因此在一些需要将输出值限制在一定范围内的任务中,Sigmoid函数是一种比较常用的选择。此外,Sigmoid函数在二分类问题中也经常被用作输出层的激活函数,可以将输出值解释为概率。
然而,Sigmoid函数也存在一些问题,其中一个主要问题是在输入值较大或较小时,函数的梯度会趋近于0,从而导致反向传播过程中的梯度消失问题。此外,Sigmoid函数的输出值不是以0为中心对称的,这可能导致一些神经元被“杀死”,即输出恒为0,从而导致模型的性能下降。
在实际应用中,由于Sigmoid函数存在上述问题,它已经逐渐被ReLU函数等其他激活函数所取代。
import torch
import torchvision
from torch import nn
from torch.nn import ReLU, Sigmoid
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
input = torch.tensor([[1, -0.5],
[-1, 3]])
input = torch.reshape(input, (-1, 1, 2, 2))
dataset = torchvision.datasets.CIFAR10("./dataset", False, torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, 64)
class Module(nn.Module):
def __init__(self):
super(Module, self).__init__()
# inplace=True:ReLU会返回一个output,input的值不变,可以保留原始数据,一般情况inplace=False
self.relu1 = ReLU(inplace=False) # 默认为 False
self.sigmoid = Sigmoid()
def forward(self, input):
output = self.sigmoid(input)
return output
mdl = Module()
writer = SummaryWriter("logs_relu")
step = 0
for data in dataloader:
img, target = data
writer.add_images("input", img, step)
output = mdl(img)
writer.add_images("output", img, step)
step += 1
writer.close()
11 nn_linear(线性层)
把图像每行首尾相接,并成一行
import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", False, torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, 64)
class Module(nn.Module):
def __init__(self):
super(Module, self).__init__()
self.linear1 = Linear(196608, 10)
def forward(self, input):
output = self.linear1(input)
return output
mdl = Module()
for data in dataloader:
img, target = data
print(img.shape)
output = torch.reshape(img, (1, 1, 1, -1))
print(output.shape)
output = mdl(output)
print(output.shape)
或者(常用):
import torch
import torchvision
from torch import nn
from torch.nn import Linear
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", False, torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, 64)
for data in dataloader:
img, target = data
print(img.shape)
output = torch.flatten(img)
print(output.shape)
12 nn_seq(Sequential)
torch.nn.Sequential
是 PyTorch 中的一个模型容器,它允许用户按照顺序组合多个神经网络层以构建神经网络模型。通过 Sequential
,可以方便地将多个层组合成一个整体,并将其作为一个单独的层或模块来使用。
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Sequential, Linear
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
dataset = torchvision.datasets.CIFAR10("./dataset", False, torchvision.transforms.ToTensor())
dataloader = DataLoader(dataset, 64)
class Module(nn.Module):
def __init__(self):
super(Module, self).__init__()
self.seq = Sequential(Conv2d(3, 32, kernel_size=(5, 5), padding=2),
MaxPool2d(kernel_size=2),
Conv2d(32, 32, kernel_size=(5, 5), padding=2),
MaxPool2d(kernel_size=2),
Conv2d(32, 64, kernel_size=(5, 5), padding=2),
MaxPool2d(kernel_size=2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.seq(x)
return x
mdl = Module()
print(mdl)
# 用 1 填充矩阵;(批次大小(batch size), 通道数(channel), 矩阵高度, 矩阵宽度)
input = torch.ones((64, 3, 32, 32))
output = mdl(input)
print(output.shape)
13 nn_loss(损失函数)
- L1 Loss和MSE Loss都是在深度学习中用于计算预测值和真实值之间差异的损失函数。
- L1 Loss,也叫做Mean Absolute Error(MAE)损失,是预测值和真实值之差的绝对值之和的平均数。它的公式是: L1 Loss = |预测值 - 真实值|
- L1 Loss相对于MSE Loss来说,更加注重outliers。当存在很多离群点(outliers)或数据分布不均匀的情况,使用L1 Loss可能会更加合适。
- MSE Loss,也称为Mean Squared Error(MSE)的损失,计算预测值和真实值之间差的平方的平均数。它的公式是: MSE Loss = (预测值 - 真实值)²,MSE损失是最常用的损失函数之一。当预测值和真实值之间的差异很小,使用MSE Loss可以帮助模型更快地收敛。
- CrossEntropyLoss是一种常用于机器学习和深度学习分类任务的损失函数。它通常用于神经网络中,例如图像分类、语音识别和自然语言处理等任务。CrossEntropyLoss测量预测概率分布与实际概率分布之间的差异。它会为错误的预测分配更高的损失,为正确的预测分配较低的损失。目标是最小化损失,使得预测的概率尽可能与实际概率匹配。CrossEntropyLoss的公式如下: L = -1/N * sum(ylog(p) + (1-y)log(1-p)) 其中,y是真实标签(0或1),p是预测概率,N是样本数,log是自然对数。
import torch
from torch.nn import L1Loss, MSELoss, CrossEntropyLoss
inputs = torch.tensor([1, 2, 3], dtype=torch.float32)
outputs = torch.tensor([1, 2, 5], dtype=torch.float32)
inputs = torch.reshape(inputs, (1, 1, 1, 3))
outputs = torch.reshape(outputs, (1, 1, 1, 3))
loss = L1Loss(reduction="sum")
result = loss(inputs, outputs)
loss_mse = MSELoss()
result_mse = loss_mse(inputs, outputs)
print(result)
print(result_mse)
x = torch.tensor([0.1, 0.2, 0.3])
y = torch.tensor([1])
x = torch.reshape(x, (1, 3))
loss_cross = CrossEntropyLoss()
result_cross = loss_cross(x, y)
print(result_cross)
14 nn_optim(优化器)
- SGD是常用的优化算法之一,用于训练神经网络。在SGD中,每次迭代时,我们从数据集中随机选择一个小批量的样本,计算这些样本的损失函数,并更新网络参数以尽量减小损失。更新的方向是梯度的相反方向,更新的大小由学习率控制,通常采用动态调整学习率的方法来提高性能。
- 具体来说,SGD的更新公式为: $$ w_{t+1} = w_t - \alpha\nabla L(w_t;x_t,y_t) $$ 其中,$w$是待更新的参数向量,$\alpha$是学习率,$L$是损失函数,$\nabla L(w_t;x_t,y_t)$是损失函数对参数的梯度。在每次迭代中,我们从数据集中随机选择一个小批量的样本$(x_t,y_t)$,然后根据当前参数$w_t$计算出该样本的损失函数的梯度,并根据上述公式更新参数$w$,直到达到预定的迭代次数或者损失函数收敛为止。 需要注意的是,SGD很容易陷入局部最优解,因此需要采用一些正则化技术来减少过拟合的风险,如L1/L2正则化、dropout等。
- CrossEntropyLoss() 是PyTorch中用于多分类任务的损失函数之一,常用于分类问题中。它是交叉熵损失函数的一种特殊形式,可以用于衡量模型预测的概率分布与真实标签的差距。 具体来说,对于一个大小为 N 的样本集合,其损失函数的计算方式为: $$ \text{loss} = -\frac{1}{N}\sum_{i=1}{N}\sum_{j=1}y_{ij}\log(p_{ij}) $$ 其中,$y_{ij}$表示第$i$个样本的第$j$个标签是否为正样本,当$y_{ij}=1$时表示正样本,否则为负样本。$p_{ij}$表示模型预测第$i$个样本的第$j$个类别的概率,它是模型的输出结果。 对于每个样本,该损失函数计算了它所有标签的损失,并对所有样本的损失求平均。当模型的预测与真实标签完全一致时,损失函数为0,否则损失函数会大于0。 在PyTorch中,可以使用 nn.CrossEntropyLoss() 来构造一个交叉熵损失函数。它会自动地对模型的输出进行softmax归一化,并计算交叉熵损失。
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
from torch.utils.tensorboard import SummaryWriter
from torch.optim.lr_scheduler import StepLR
from torch.utils.data import DataLoader
"""
demo1:在模型训练中加入损失函数和优化器
"""
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
dataloader = DataLoader(dataset, batch_size=1)
class Module(nn.Module):
def __init__(self):
super(Module, 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
mld = Module()
# 损失函数 优化器
loss = nn.CrossEntropyLoss()
# 调用 mld.parameters() 可以获取到 Module 中的所有可训练参数;lr是训练速率
optim = torch.optim.SGD(mld.parameters(), lr=0.01)
for i in range(20):
running_loss = 0.0
for data in dataloader:
imgs, targets = data
outputs = mld(imgs)
result_loss = loss(outputs, targets)
# 注意 清零--》反向传播算梯度--》更新参数
optim.zero_grad()
result_loss.backward()
optim.step()
running_loss = running_loss + result_loss
print(running_loss)
# running_loss:
# tensor(18726.5977, grad_fn=<AddBackward0>)
# tensor(16132.8926, grad_fn=<AddBackward0>)
# tensor(15426.6357, grad_fn=<AddBackward0>)
15 model_pretrained(现有网络模型使用与修改)
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
"""
demo1:加载vgg训练好的模型,并在里边加入一个线性层
"""
# ImageNet数据集太大了,100多G,还是用CIFAR10吧
# train_data = torchvision.datasets.ImageNet("../data_image_net", split='train', download=True,
# transform=torchvision.transforms.ToTensor())
train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(),
download=True)
# 加载现有的vgg模型
vgg16_not_pretrain = torchvision.models.vgg16(pretrained=False)
vgg16_pretrained = torchvision.models.vgg16(pretrained=True)
# 修改方法1:加入一个线性层,编号7
vgg16_pretrained.add_module("7", nn.Linear(1000, 10))
print(vgg16_pretrained)
# 修改方法2:修改原来的第六个线性层
vgg16_not_pretrain.classifier[6] = nn.Linear(4096, 10)
print(vgg16_not_pretrain)
16 model_save(模型的保存与加载)
import torch
import torchvision
from torch import nn
from torch.nn import Conv2d, MaxPool2d, Flatten, Linear, Sequential
train_data = torchvision.datasets.CIFAR10('../data', train=True, transform=torchvision.transforms.ToTensor(),
download=True)
# 加载现有的vgg模型
vgg16_not_pretrain = torchvision.models.vgg16(pretrained=False)
vgg16_pretrained = torchvision.models.vgg16(pretrained=True)
"""
demo2:保存/加载模型
两种保存方法 对应两种加载方法
保存模型都是用torch.save,加载模型都是用torch.load,一起保存的时候save整个模型,加载时直接torch.load加载
保存时只保存参数的,需要先向model vgg加载结构,再用model vgg.load state dict加载参数,加载参数还是要torc.load方法
保存方法1的‘陷阱’:
在使用方法1保存现有模型时,不会出错,代码更少,但是使用方法1保存自己的模型时,必须要引入这个模型的定义才可以
"""
# 保存东西需要现有东西保存
vgg16 = torchvision.models.vgg16(pretrained=False)
# 保存方式1:模型结构+参数一起保存
torch.save(vgg16, "vgg16_pretrained_save_method1.pth")
# 多保存一个vgg16_pretrained,后面 完整模型测试讨论会用到
torch.save(vgg16_pretrained, "vgg16_pretrained_save_method1.pth")
# 加载方式1
model1 = torch.load("vgg16_save_method1.pth")
# 保存方式2:只保存模型参数(推荐)
torch.save(vgg16.state_dict(), "vgg16_save_method2.pth")
# 加载方式2
# 先加载模型结构
model_vgg = torchvision.models.vgg16(pretrained=False)
# 再加载模型参数
model_vgg.load_state_dict(torch.load("vgg16_save_method2.pth"))
# 保存方法1的‘陷阱’
"""先保存tudui模型
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()
torch.save(tudui, "tudui_save_method1.pth")
"""
"""
直接加载tudui模型会报错
tudui = torch.load("tudui_save_method1.pth")
报错:
AttributeError: Can't get attribute 'Tudui' ...>
"""
17 完整训练流程
- model.py
import torch
from torch import nn
from torch.nn import Sequential, Conv2d, ReLU, MaxPool2d, Flatten, Linear
# 创建网络模型
class Module(nn.Module):
def __init__(self):
super(Module, self).__init__()
self.model1 = Sequential(
Conv2d(3, 32, (5, 5), (1, 1), 2),
ReLU(),
MaxPool2d(2),
Conv2d(32, 32, (5, 5), (1, 1), 2),
ReLU(),
MaxPool2d(2),
Conv2d(32, 64, (5, 5), (1, 1), 2),
ReLU(),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)
def forward(self, x):
x = self.model1(x)
return x
if __name__ == '__main__':
mdl = Module()
inputs = torch.ones((64, 3, 32, 32))
outputs = mdl(inputs)
print(outputs.shape)
- trian.py
import os
import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
from model import Module
# 定义训练的设备
# cuda:0 是独显,因为集显没有cuda
# cuda:0 可以替换成 cuda,对于单显卡的 pc 没有区别
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
train_data = torchvision.datasets.CIFAR10("./dataset", train=True, transform=torchvision.transforms.ToTensor(),
download=True)
test_data = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=True)
train_data_size = len(train_data)
test_data_size = len(test_data)
print(f"训练数据集长度:{train_data_size}")
print(f"测试数据集长度:{test_data_size}")
# 使用dataloader加载数据
train_dataloader = DataLoader(train_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)
# 创建模型
mdl = Module()
mdl = mdl.to(device)
# 损失函数
loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.to(device)
# 优化器
learning_rate = 0.01
optimizer = torch.optim.SGD(mdl.parameters(), lr=learning_rate)
# 设置控制训练次数的参数
# 记录训练 测试次数
total_train_step = 0
total_test_step = 0
# 训练轮数
epoch = 20
# 记录最佳准确率
best_accuracy = 0
# 写入board
writer = SummaryWriter("logs_train")
for i in range(epoch):
print(f"-------第 {i + 1} 轮训练开始-------")
# 训练步骤开始
mdl.train()
for data in train_dataloader:
imgs, targets = data
imgs = imgs.to(device)
targets = targets.to(device)
outputs = mdl(imgs)
loss = loss_fn(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step += 1
writer.add_scalar("train loss", loss.item(), total_train_step)
if total_train_step % 100 == 0:
print("训练次数:{}, loss:{}".format(total_train_step, loss.item()))
# 测试步骤开始
mdl.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 = mdl(imgs)
loss = loss_fn(outputs, targets)
total_test_loss += loss
accuracy = (outputs.argmax(1) == targets).sum()
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)
now_accuracy = total_accuracy / test_data_size
writer.add_scalar("test accuracy", total_accuracy / test_data_size, total_test_step)
total_test_step += 1
model_folder = "./model"
if not os.path.exists(model_folder):
os.makedirs(model_folder)
# 保存准确率最高的模型
if now_accuracy > best_accuracy:
best_accuracy = now_accuracy
torch.save(mdl.state_dict(), f"./model/best.pth")
# 保存最新的模型
if i == epoch - 1:
torch.save(mdl.state_dict(), f"./model/last.pth")
writer.close()
18 完整推理流程
- 找一张狗的图片,放在./data/images/dog.jpg
import torch
import torchvision
from PIL import Image
from model import Module
# 加载数据集
dataset = torchvision.datasets.CIFAR10("./dataset", train=False, transform=torchvision.transforms.ToTensor(),
download=False)
# 获取分类列表
class_list = list(dataset.class_to_idx.keys())
# 加载图片
image_path = "./data/images/dog.jpg"
image = Image.open(image_path)
'''
image = image.convert('RGB')
由于图片有png jpg的不同格式,而png图片是四通道的 多一个透明度通道,jpg是三通道的 只有三个颜色通道
这一行代码可以让png jpg都只有三个颜色通道,增强了代码的适应性
'''
image = image.convert('RGB')
transform = torchvision.transforms.Compose([torchvision.transforms.Resize((32, 32)),
torchvision.transforms.ToTensor()])
image = transform(image)
print(image.shape)
# 加载模型
model = Module()
model.load_state_dict(torch.load("./model/best.pth", map_location=torch.device('cuda')))
print(model)
image = torch.reshape(image, (1, 3, 32, 32))
# 查看模型输出
model.eval()
with torch.no_grad():
output = model(image)
# 所有类别的概率
print(output)
# 找到最大的概率值的位置 查看数字对应类别在debug datasets.CIFAR10的class to idx
print(output.argmax(1))
# 输出预测结果
print(class_list[output.argmax(1)])
标签:torchvision,nn,self,torch,笔记,PyTorch,import,data
From: https://www.cnblogs.com/xiufanivan/p/17408281.html