Pytorch快速入门笔记

本笔记从小土堆Pytorch教程中记录一些实用的Pytorch相关操作.

1. 加载数据

1.1 PIL

PIL类可以用于加载图像、保存图像等操作

1
2
from PIL import Image
img = Image.open('data/hymenoptera_data/train/ants/342438950_a3da61deab.jpg')

1.2 DataSet

DataSet是一个抽象类,需要实现其中的__getitem__方法,以及最好是实现__len__方法,不然不能用迭代器,用for循环的方式取数据,
以下是一个自定义数据集的设置方式,可以看到需要重写__getitem__方法取数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyDataSet(Dataset):

def __init__(self, data_dir, transforms=None):
super().__init__()
self.data_dir = data_dir
self.image_paths = os.listdir(data_dir)
self.transforms = transforms

def __getitem__(self, index) -> T_co:
img_file_name = self.image_paths[index]
img = Image.open(self.data_dir + img_file_name)
img_trans = self.transforms(img)
return img_trans

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

1.3 DataLoader

如果把DataSet看做一副牌,那么DataLoader就是用于定义如何发牌,或者对牌进行一些操作(洗牌、转换格式等),如果已经有一个数据集,那么可以通过这种方式定义data_loader

1
2
3
4
my_ds = MyDataSet(data_dir='data/hymenoptera_data/val/ants/', transforms=trans)
# drop_last: 总长度除bs 除不尽的时候是否去掉最后一个
# batch_size: 批的量
data_loader = DataLoader(dataset=my_ds, batch_size=2, shuffle=True, drop_last=True)

定义好的数据集,可以通过DataLoader加载,并通过for循环取数据,例如:

1
2
3
4
5
step = 0
for images in data_loader:
print(images.shape)
writer.add_images('image_batch', images, step) # (tag,Image,step(不添加这个参数 tensorboard里面的step始终为零))
step += 1

1.4 torchvision数据集的下载和使用

如果是一些成熟的数据集,比如CIFAR10,可以用封装好的方式获取数据集,这些数据集也是重写了DataSet类,可以传入transform

1
2
3
4
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)

2. Tensorboard 的使用

TensorBoard是一个可视化工具,它可以用来展示网络图、张量的指标变化、张量的分布情况等。特别是在训练网络的时候,我们可以设置不同的参数(比如:权重W、偏置B、卷积层数、全连接层数等),使用TensorBoader可以很直观的帮我们进行参数的选择。它通过运行一个本地服务器,来监听6006端口。在浏览器发出请求时,分析训练时记录的数据,绘制训练过程中的图像。

首先定义一个SummaryWriter(),然后就可以用writer里面的方法往tensorboard里面写数据,不仅可以添加过程量还可以添加单张图像。默认的路径保存到本地runs目录下,可以用SummaryWriter()

1
2
3
4
5
6
7
8
writer = SummaryWriter()
# 添加过程量(标量)
for n_iter in range(100):
writer.add_scalar('Loss/train', np.random.random(), n_iter)
writer.add_scalar('Loss/test', np.random.random(), n_iter)
writer.add_scalar('Accuracy/train', np.random.random(), n_iter)
writer.add_scalar('Accuracy/test', np.random.random(), n_iter)
writer.add_image(tag='test', img_tensor=img_tensor)

查看数据:cd到保存文件的文件夹下,输入tensorboard --logdir runs runs对应文件保存的目录,然后就可以通过访问http://localhost:6006/#timeseries查看记录的结果

3. Transforms 的使用

Transforms用来对一张图片进行一系列的转换,可以用Compose定义需要转换的内容

1
2
3
4
5
6
trans = transforms.Compose([
transforms.ToTensor(),
# transforms.RandomCrop(size=(50,50)) 随机裁剪
transforms.Resize((100, 100))
])

定义好转换之后,可以对单张图片进行转换,把图像传入就可以,例如:

1
2
3
4
5
6
7
8
# 添加图像
img = Image.open('data/hymenoptera_data/train/ants/342438950_a3da61deab.jpg')
# 图像转换
trans = transforms.Compose([
transforms.ToTensor(),
# transforms.RandomCrop(size=(50,50)) # 随机裁剪
])
img_tensor = trans(img)

4. 常用函数

4.1卷积函数

卷积函数的定义网上有很多了就不再赘述了,定义一个卷积核,然后和现在的矩阵进行卷积操作,可得到一个结果。

借用知乎2D卷积,nn.Conv2d和F.conv2d一段话:卷积操作:卷积核和扫过的小区域对应位置相乘再求和的操作,卷积完成后一般要加个偏置bias。一种Kernel如果分成多个通道上的子Kernel做卷积运算,最后运算结果还要加在一起后,再加偏置。

使用卷积运算的时候需要注意输入输出的尺寸,需要对齐,比如Conv2D 如果是函数就要求B ,C 两个维度要对齐。
需要注意的点是输入输出维度会根据stride、padding的设置改变,比如64×64的图像进去,不设置padding出来的图像可能就变成62×62了,如果还要保持图像尺寸一致(特别是复现论文的场景),需要反算一下stride和padding的值,这里公式在Pytorch Conv2d文档,需要的时候直接查阅就好。

关于可视化展示卷积函数中的stride、padding、dilation参数的含义,可参考文档:Convolution arithmetic

1
2
3
4
5
6
7
8
9
10
11
12
13
import torch.nn.functional as F

w = torch.rand(1, 3, 2, 2) # 对应 B C ×卷积核的 H W,前2个维度对应上(在H W两个维度上进行卷积)
res = F.conv2d(input=a, weight=w, stride=1)
print(res)

tensor([[[[-0.9163, -1.4657, -3.6013, ..., 0.1913, -1.4308, -1.1725],
[-1.7863, -1.1487, -3.5197, ..., -0.5010, -2.3962, -3.8177],
[-0.0863, -0.3723, -1.7177, ..., -1.9196, -1.4938, -2.6761],
...,
[ 0.8136, -4.5267, -0.6807, ..., -2.2519, 1.4239, -0.9793],
[ 1.8353, -1.8440, -3.9382, ..., -1.8193, 2.7279, 4.4726],
[ 0.5444, 1.2673, -3.4205, ..., -2.3179, -2.5870, -1.7544]]]])

4.2 池化函数

池化函数是深度学习中常用的技术,主要用于降低数据的维度和减少计算量。常见的池化函数包括:

  1. 最大池化(Max Pooling):在池化窗口内选取最大值作为输出,能够提取图像中的主要特征。
  2. 平均池化(Average Pooling):在池化窗口内取平均值作为输出,可以平滑输入数据,减少噪声的影响。
  3. 自适应池化(Adaptive Pooling):根据输入的大小自动调整池化窗口的大小,以适应不同的输入尺寸。

池化操作可以分为一维池化、二维池化和三维池化,具体取决于被池化的张量维数。池化不仅可以减小数据大小,还可以增加数据大小,具体取决于应用场景。

1
2
3
a = torch.randn(1, 3, 64, 64)
res = F.max_pool2d(a,kernel_size=2) # kernel_size指定2的话是默认2×2的2d(正方形)。而且池化默认区域不重叠的,默认步长就是kernel_size=2,这一点和卷积运算不一样
print(res.shape) # torch.Size([1, 3, 32, 32])

5. 神经网络的搭建

5.1 卷积层、池化层、非线性激活层

通过引入torch.nn引入常见神经网络的层,包括卷积层、池化层等.以及非线性激活层,RELU SOFTMAX之类的,具体就不再展开了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyNeuralNetwork(nn.Module):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.conv1 = nn.Conv2d(3, 64, 3, 1, 1)
self.conv2 = nn.Conv2d(3, 64, 3, 1, 0)
self.max_pool = nn.MaxPool2d(4)
self.relu = nn.ReLU()
self.softmax = nn.Softmax()
# in_channels: int,
# out_channels: int,
# kernel_size: _size_2_t,
# stride: _size_2_t = 1,
# padding: Union[str, _size_2_t] = 0,
# dilation: _size_2_t = 1,

a = torch.randn(1, 3, 64, 64)

mnn = MyNeuralNetwork()

res_conv1 = mnn.conv1(a)
print(res_conv1.shape) # torch.Size([1, 64, 64, 64])

res_conv2 = mnn.conv2(a)
print(res_conv2.shape) # torch.Size([1, 64, 62, 62])

res_max_pool = mnn.max_pool(a)
print(res_max_pool.shape) # torch.Size([1, 3, 16, 16])


5.2线性层及其他层

  1. 线性层:线性层又叫全连接层,其中每个神经元和上一层所有的神经元相连,使用nn.Linear(in_features,out_features,bias)定义,运算公式是 $$y=xA^T+b$$ ,注意默认是加上bias的,即bias=True
    代码例子:
1
2
3
4
5
6
class MyNeuralNetwork(nn.Module):
def __init__(self, *args, **kwargs) -> None:
self.linear = nn.Linear(64,32)
a = torch.randn(1, 3, 64, 64)
res_l = mnn.linear(a)
print(res_l.shape) # torch.Size([1, 3, 64, 32])
  1. 展平层:将多维度的张量展平。默认参数:start_dim: int = 1, end_dim: int = -1 从开始的维度展开到结束的维度
    代码例子:
1
2
3
4
5
6
7
8
9
class MyNeuralNetwork(nn.Module):
def __init__(self, *args, **kwargs) -> None:
self.flatten1 = nn.Flatten(start_dim=0)
self.flatten2 = nn.Flatten()
a = torch.randn(1, 3, 64, 64)
res_f = mnn.flatten1(a)
res_f2 = mnn.flatten2(a)
print(res_f.shape) # torch.Size([12288])
print(res_f2.shape) # torch.Size([1, 12288])

一般说来,Flatten层常用于把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten默认不影响batch的大小(start_dim =1 )。

5.3 Sequential的使用

nn.Sequential() 可以作为容器,里面放入模型的各种层,在forward的时候将会贯序列执行各层,通常有2种定义方式

  • 定义方式1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import torch.nn as nn
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)

print(model)
print(model[2]) # 通过索引获取第几个层
'''运行结果为:
Sequential(
(0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(1): ReLU()
(2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
(3): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''
  • 定义方式2:给每个层添加一个名称
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import torch.nn as nn
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))

print(model)
'''运行结果为:
Sequential(
(conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(relu1): ReLU()
(conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
(relu2): ReLU()
)
'''

我们可以将前面所学的层组合起来,形成深层神经网络的架构,例如我们可以编写一个自己的网络如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class MyNeuralNetwork(nn.Module):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.model = nn.Sequential(
nn.Conv2d(3,64,3,1,1), # 【1,64,64,64】 备注:ks=3,stride=1,padding = 1
# Hout(64) = (Hin(64) + 2×padding - dilation×[ks - 1] × 1 )/stride + 1
nn.ReLU(),
nn.Conv2d(64,32,3,1,1), # 1,32,64,64
nn.ReLU(),
nn.Conv2d(32, 16, 3,1,1), # 1,16,64,64
nn.ReLU(),
nn.MaxPool2d(2), # 1,16,32,32
nn.ReLU(),
nn.Linear(32,1024), # 1,16,32,1024
nn.ReLU(),
nn.Linear(1024, 1024), # 1,16,1024,1024
nn.ReLU(),
nn.Flatten(), # 1,524288
nn.Linear(524288,10), # 1,10
nn.Softmax(dim= -1)
)

mnn = MyNeuralNetwork()

a = torch.randn(1,3,64,64)

res = mnn.model(a)

print(res.shape) # torch.Size([1, 10])
print(res) #tensor([[0.0991, 0.0982, 0.0997, 0.0996, 0.1009, 0.1030, 0.0988, 0.1027, 0.0991,0.0988]], grad_fn=<SoftmaxBackward0>)

通过使用Sequential()的方式可以便捷的完成网络的定义,快速实现网络。

5.4 小网络搭建实战

以vgg16这个网络(图待补充)为例,搭建模型如下(暂未添加Relu层),其实和我们之前写的模型很像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyNeuralNetwork(nn.Module):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
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(4096,64),
nn.Linear(64, 10),
)

a = torch.randn(1, 3, 64, 64)
mnn = MyNeuralNetwork()
res = mnn.model(a)
print(res.shape) #torch.Size([1, 10])

6 损失函数与反向传播

6.1 损失函数

损失函数(Loss Function)是一个衡量预测结果与真实结果之间差异的函数 ,也称为误差函数。它通过计算模型的预测值与真实值之间的不一致程度,来评估模型的性能.
根据任务不同,选择的损失函数也不同,对于回归任务,常见的损失函数有MSELoss,对于分类任务常见的损失函数有交叉熵损失CrossEntropyLoss
交叉熵的损失函数可以描述为 $$loss(x,class) = -log(exp(x[class]/sum_j(exp(x[j])))=-x[class]+ln(sum_j(exp(x[j])]))$$
举例说明:

1
2
3
4
5
import torch.nn.functional as F
x = torch.tensor([[0.1, 0.2, 0.3]]) # 预测三个类别概率分别是0.1,0.2,0.3
y = torch.tensor([1]) # 答案是1
loss = F.cross_entropy(x, y) # 计算交叉熵 loss = -0.2 + ln(e^0.1+e^0.2+e^0.3) = 1.10194284823
print(loss) # tensor(1.1019)

其他的案例也差不多

1
2
3
4
5
6
7
8
9
10
x = torch.tensor([[0.55, 0.88]]) # 预测值
y = torch.tensor([[0.5, 0.8]]) # 真实值
loss_l1 = F.l1_loss(x, y) # L1Loss 一阶距
loss_mse = F.mse_loss(x, y) # MSE_LOSS
print(loss_l1) # tensor(0.0650)
print(loss_mse) # tensor(0.0044)

loss_layer = nn.L1Loss(reduction='sum') # 备注:reduction默认是mean,用mean的话结果是0.065
loss_l1_by_layer = loss_layer(x, y)
print(loss_l1_by_layer) # tensor(0.1300)

6.2 反向传播

首先说一下什么是反向传播算法。
反向传播算法(Backpropagation,简称BP算法)是“误差反向传播”的简称,是适合于多层神经元网络的一种学习算法,它建立在梯度下降法的基础上。梯度下降法是训练神经网络的常用方法,许多的训练方法都是基于梯度下降法改良出来的,因此了解梯度下降法很重要。梯度下降法通过计算损失函数的梯度,并将这个梯度反馈给最优化函数来更新权重以最小化损失函数。

在PyTorch中,loss.backward()函数用于计算模型参数相对于损失函数的梯度。

前向传播

首先,模型通过前向传播计算输出值。在这个过程中,PyTorch会记录计算图(Computation Graph),这个计算图记录了从输入到输出的每一步运算及其依赖关系。每个张量(Tensor)都有一个.grad_fn属性,指向一个函数,这个函数描述了如何计算这个张量关于其输入的梯度。

反向传播

当调用loss.backward()时,PyTorch开始反向遍历计算图。这个过程从损失函数开始,沿着图反向传播误差,计算每一个参与运算的张量关于损失的梯度。这是通过链式法则(Chain Rule)完成的,即将损失对某个中间变量的导数分解为其后续操作导数的乘积。

梯度计算
在反向传播过程中,每个运算都会计算其输出关于输入的梯度,并将这个梯度累积到输入张量的.grad属性中(如果是标量损失,它没有.grad属性)。这意味着如果一个张量被多个路径使用,它的.grad属性会累积从所有路径来的梯度.

在使用loss.backward()时,有几个重要的注意事项:

梯度归零:在每次反向传播之前,通常需要调用optimizer.zero_grad()来将梯度归零,以避免梯度累加

6.3 优化器

优化器决定了模型以何种方式的梯度下降算法更新模型。常见的优化器有 SGD, adam等。

在PyTorch中,optimizer.step()是优化器对象的一个方法,用于执行模型参数的更新。在深度学习训练过程中,参数更新是通过反向传播算法计算损失函数的梯度后,使用优化器根据这些梯度进行的。optimizer.step()方法正是用于根据梯度和学习率等超参数来更新模型参数,从而使损失函数值最小化的步骤

7 使用常用模型

7.1 使用库的方式调用常用模型

一些常用的模型,比较经典的模型都是包装在库里面了,可以通过torchvision.models.xxx调用模型.
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
vgg16 = torchvision.models.vgg16(pretrained = False)

print(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)
)
)

7.2 对常用模型进行增加或修改

  1. 增加某层
1
2
3
4
5
6
7
8
9
10
11
12
13
vgg16.features.add_module('relu',torch.nn.ReLU())
vgg16.classifier.add_module('linear',torch.nn.Linear(1000,10))
例如分类器加上之后,模型结构如下:
(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)
(linear): Linear(in_features=1000, out_features=10, bias=True)
)
  1. 修改某层
1
2
3
4
5
6
7
8
9
10
11
12
13
vgg16.classifier[7]=nn.Linear(1000,20) # 7 是模型的第几层

上面的代码执行之后会对刚添加的线性层修改输出特征节点的个数,改成了55
(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)
(linear): Linear(in_features=1000, out_features=55, bias=True)
)
  1. 删除某层
    如果想删除某一层,直接将其删除即可,命令为
1
del vgg16.classifier[7]
  1. 冻结部分层
    我们现在只想训练最后的fc1层,然后就有了下面的
1
2
3
4
5
6
7
# 冻结fc1层的参数
for name, param in model.named_parameters():
if "fc1" in name:
param.requires_grad = False

# 只传入需要更新的参数给优化器
optimizer = optim.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-2)

8 完整的训练流程

包括数据集准备,dataLoader准备、网络构建、损失函数定义、循环、计算误差、tensorboard可视化等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
import torch.nn
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

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)

train_data_size = len(train_data)
test_data_size = len(test_data)

train_data_loader = DataLoader(train_data, batch_size=64)
test_data_loader = DataLoader(test_data, batch_size=64)


class MyNeuralNetwork(nn.Module):

def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
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):
return self.model(x)


mnn = MyNeuralNetwork()
if torch.cuda.is_available():
mnn = mnn.cuda()

loss_fn = nn.CrossEntropyLoss()
loss_fn = loss_fn.cuda()

lr = 1e-2
optim = torch.optim.SGD(mnn.parameters(), lr=lr)

writer = SummaryWriter('../logs_train')
epoch = 10
for i in range(epoch):
mnn.train()
train_step = 0
for data in train_data_loader:
imgs, targets = data
imgs = imgs.cuda()
targets = targets.cuda()
outputs = mnn(imgs)
loss = loss_fn(outputs, targets)

optim.zero_grad()
loss.backward()
optim.step()

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

mnn.eval()
total_test_loss = 0
total_accuracy = 0
total_test_step = 0
with torch.no_grad():

for data in test_data_loader:
imgs, targets = data
imgs = imgs.cuda()
targets = targets.cuda()
outputs = mnn(imgs)
loss = loss_fn(outputs, targets)
total_test_loss += loss.item()
# 求正确率
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)
writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)
total_test_step = total_test_step + 1

# 保存模型
torch.save(mnn, "mnn{}.pth".format(i))
print("模型已保存")

writer.close()

运行代码的效果:

1
2
3
4
5
6
7
8
9
10
训练次数:100,loss = 2.2861804962158203
训练次数:200,loss = 2.2651848793029785
训练次数:300,loss = 2.2193050384521484
训练次数:400,loss = 2.1087183952331543
训练次数:500,loss = 2.0523011684417725
训练次数:600,loss = 1.9955447912216187
训练次数:700,loss = 1.9990053176879883
整体测试集上的loss:319.1352970600128
整体测试集上的正确率:0.2669000029563904
模型已保存

8.1 使用GPU训练

除了8.1的方式在所有张量用.cuda()送入显存的方式使用GPU训练外,还可以用tensor.to(device)的方式送入显存

1
2
3
4
device = 'cuda' if torch.cuda.is_available() else 'cpu'

net = Net().to(device)
imgs = imgs.to(device)

Pytorch快速入门笔记
https://runsstudio.github.io/2025/04/02/Pytorch快速上手/
发布于
2025年4月2日
许可协议